]>
Commit | Line | Data |
---|---|---|
866f8763 A |
1 | /* |
2 | * Copyright (c) 2013-2017 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
d8f41ccd A |
24 | #import <Accounts/Accounts.h> |
25 | #import <Accounts/ACAccountStore_Private.h> | |
822b670c | 26 | #import <Accounts/ACAccountType_Private.h> |
5c19dc3a | 27 | #import <AggregateDictionary/ADClient.h> |
822b670c | 28 | #import <AppSupport/AppSupportUtils.h> |
d8f41ccd | 29 | #import <AppleAccount/AppleAccount.h> |
d8f41ccd | 30 | #import <AppleAccount/ACAccountStore+AppleAccount.h> |
822b670c A |
31 | #import <CloudServices/SecureBackup.h> |
32 | #import <CoreFoundation/CFUserNotification.h> | |
d8f41ccd | 33 | #import <Foundation/Foundation.h> |
d64be36e | 34 | #import <ManagedConfiguration/ManagedConfiguration.h> |
822b670c A |
35 | #import <MobileCoreServices/MobileCoreServices.h> |
36 | #import <MobileCoreServices/LSApplicationWorkspace.h> | |
37 | #import <MobileGestalt.h> | |
b54c578e | 38 | #import <os/transaction_private.h> |
822b670c A |
39 | #import <ProtectedCloudStorage/CloudIdentity.h> |
40 | #import <Security/SecFrameworkStrings.h> | |
41 | #import <SpringBoardServices/SBSCFUserNotificationKeys.h> | |
d8f41ccd | 42 | #include <dispatch/dispatch.h> |
b54c578e A |
43 | #include "keychain/SecureObjectSync/SOSCloudCircle.h" |
44 | #include "keychain/SecureObjectSync/SOSCloudCircleInternal.h" | |
45 | #include "keychain/SecureObjectSync/SOSPeerInfo.h" | |
46 | #include "keychain/SecureObjectSync/SOSInternal.h" | |
d8f41ccd A |
47 | #include <notify.h> |
48 | #include <sysexits.h> | |
49 | #import "Applicant.h" | |
50 | #import "NSArray+map.h" | |
5c19dc3a | 51 | #import "PersistentState.h" |
d8f41ccd A |
52 | #include <xpc/private.h> |
53 | #include <sys/time.h> | |
54 | #import "NSDate+TimeIntervalDescription.h" | |
d8f41ccd A |
55 | #include <xpc/activity.h> |
56 | #include <xpc/private.h> | |
fa7225c8 | 57 | #import "os/activity.h" |
d8f41ccd | 58 | #import <syslog.h> |
5c19dc3a A |
59 | #include "utilities/SecCFRelease.h" |
60 | #include "utilities/debugging.h" | |
fa7225c8 | 61 | #include "utilities/SecAKSWrappers.h" |
6b200bc3 | 62 | #include "utilities/SecCFWrappers.h" |
8a50f688 | 63 | #include <utilities/SecXPCError.h> |
7fb2cbd2 | 64 | #import <os/variant_private.h> |
fa7225c8 A |
65 | |
66 | #import "CoreCDP/CDPFollowUpController.h" | |
67 | #import "CoreCDP/CDPFollowUpContext.h" | |
866f8763 | 68 | #import <CoreCDP/CDPAccount.h> |
d8f41ccd A |
69 | |
70 | // As long as we are logging the failure use exit code of zero to make launchd happy | |
b54c578e | 71 | #define EXIT_LOGGED_FAILURE(code) exit(0) |
d8f41ccd A |
72 | |
73 | const char *kLaunchLaterXPCName = "com.apple.security.CircleJoinRequestedTick"; | |
74 | CFRunLoopSourceRef currentAlertSource = NULL; | |
75 | CFUserNotificationRef currentAlert = NULL; | |
76 | bool currentAlertIsForApplicants = true; | |
77 | bool currentAlertIsForKickOut = false; | |
78 | NSMutableDictionary *applicants = nil; | |
79 | volatile NSString *debugState = @"main?"; | |
80 | dispatch_block_t doOnceInMainBlockChain = NULL; | |
fa7225c8 A |
81 | bool _isLocked = true; |
82 | bool processApplicantsAfterUnlock = false; | |
83 | bool _unlockedSinceBoot = false; | |
866f8763 A |
84 | bool _hasPostedFollowupAndStillInError = false; |
85 | bool _isAccountICDP = false; | |
ecaf5866 | 86 | bool _executeProcessEventsOnce = false; |
d8f41ccd | 87 | |
866f8763 A |
88 | NSString *castleKeychainUrl = @"prefs:root=APPLE_ACCOUNT&path=ICLOUD_SERVICE/com.apple.Dataclass.KeychainSync/ADVANCED"; |
89 | NSString *rejoinICDPUrl = @"prefs:root=APPLE_ACCOUNT&aaaction=CDP&command=rejoin"; | |
d8f41ccd | 90 | |
fa7225c8 A |
91 | BOOL processRequests(CFErrorRef *error); |
92 | ||
b54c578e A |
93 | static BOOL isErrorFromXPC(CFErrorRef error) |
94 | { | |
95 | // Error due to XPC failure does not provide information about the circle. | |
96 | if (error && (CFEqual(sSecXPCErrorDomain, CFErrorGetDomain(error)))) { | |
97 | secnotice("cjr", "XPC error while checking circle status: \"%@\", not processing events", error); | |
98 | return YES; | |
99 | } | |
100 | return NO; | |
101 | } | |
102 | ||
866f8763 A |
103 | static void PSKeychainSyncIsUsingICDP(void) |
104 | { | |
105 | ACAccountStore *accountStore = [[ACAccountStore alloc] init]; | |
106 | ACAccount *account = [accountStore aa_primaryAppleAccount]; | |
107 | NSString *dsid = account.accountProperties[@"personID"]; | |
108 | BOOL isICDPEnabled = NO; | |
109 | if (dsid) { | |
110 | isICDPEnabled = [CDPAccount isICDPEnabledForDSID:dsid]; | |
111 | NSLog(@"iCDP: PSKeychainSyncIsUsingICDP returning %@", isICDPEnabled ? @"TRUE" : @"FALSE"); | |
112 | } else { | |
113 | NSLog(@"iCDP: no primary account"); | |
114 | } | |
115 | ||
116 | _isAccountICDP = isICDPEnabled; | |
117 | secnotice("cjr", "account is icdp: %d", _isAccountICDP); | |
118 | } | |
fa7225c8 A |
119 | |
120 | static void keybagDidLock() | |
121 | { | |
122 | secnotice("cjr", "keybagDidLock"); | |
123 | } | |
124 | ||
125 | static void keybagDidUnlock() | |
126 | { | |
127 | secnotice("cjr", "keybagDidUnlock"); | |
128 | ||
129 | CFErrorRef error = NULL; | |
b54c578e | 130 | |
fa7225c8 A |
131 | if(processApplicantsAfterUnlock){ |
132 | processRequests(&error); | |
133 | processApplicantsAfterUnlock = false; | |
134 | } | |
866f8763 A |
135 | |
136 | SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircle(&error); | |
b54c578e A |
137 | BOOL xpcError = isErrorFromXPC(error); |
138 | if(xpcError && circleStatus == kSOSCCError) { | |
139 | secnotice("cjr", "returning early due to error returned from securityd: %@", error); | |
8a50f688 A |
140 | return; |
141 | } | |
142 | else if (_isAccountICDP && (circleStatus == kSOSCCError || circleStatus == kSOSCCCircleAbsent || circleStatus == kSOSCCNotInCircle) && _hasPostedFollowupAndStillInError == false) { | |
866f8763 A |
143 | NSError *localError = nil; |
144 | CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair]; | |
145 | CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init]; | |
b3971512 A |
146 | |
147 | secnotice("followup", "Posting a follow up (for SOS) of type repair"); | |
866f8763 A |
148 | [cdpd postFollowUpWithContext:context error:&localError ]; |
149 | secnotice("cjr", "account is icdp"); | |
150 | if(localError){ | |
151 | secnotice("cjr", "request to CoreCDP to follow up failed: %@", localError); | |
152 | } | |
153 | else{ | |
154 | secnotice("cjr", "CoreCDP handling follow up"); | |
155 | _hasPostedFollowupAndStillInError = true; | |
156 | } | |
157 | } | |
158 | else if(_isAccountICDP && circleStatus == kSOSCCInCircle){ | |
159 | _hasPostedFollowupAndStillInError = false; | |
160 | } | |
161 | else{ | |
162 | secnotice("cjr", "account not icdp"); | |
163 | } | |
fa7225c8 A |
164 | } |
165 | ||
166 | static bool updateIsLocked () | |
167 | { | |
168 | CFErrorRef aksError = NULL; | |
169 | if (!SecAKSGetIsLocked(&_isLocked, &aksError)) { | |
170 | _isLocked = YES; | |
171 | secerror("Got error querying lock state: %@", aksError); | |
172 | CFReleaseSafe(aksError); | |
173 | return NO; | |
174 | } | |
175 | if (!_isLocked) | |
176 | _unlockedSinceBoot = YES; | |
177 | return YES; | |
178 | } | |
179 | ||
180 | static void keybagStateChange () | |
181 | { | |
182 | secerror("osactivity initiated"); | |
183 | os_activity_initiate("keybagStateChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{ | |
184 | BOOL wasLocked = _isLocked; | |
185 | if ( updateIsLocked()) { | |
186 | if (wasLocked == _isLocked) | |
187 | secerror("still %s ignoring", _isLocked ? "locked" : "unlocked"); | |
188 | else if (_isLocked) | |
189 | keybagDidLock(); | |
190 | else | |
191 | keybagDidUnlock(); | |
192 | } | |
193 | }); | |
194 | } | |
195 | ||
d8f41ccd A |
196 | static void doOnceInMain(dispatch_block_t block) |
197 | { | |
198 | if (doOnceInMainBlockChain) { | |
199 | doOnceInMainBlockChain = ^{ | |
200 | doOnceInMainBlockChain(); | |
201 | block(); | |
202 | }; | |
203 | } else { | |
204 | doOnceInMainBlockChain = block; | |
205 | } | |
206 | } | |
207 | ||
5c19dc3a | 208 | |
d8f41ccd A |
209 | static NSString *appleIDAccountName() |
210 | { | |
5c19dc3a | 211 | ACAccountStore *accountStore = [[ACAccountStore alloc] init]; |
d8f41ccd A |
212 | ACAccount *primaryAppleAccount = [accountStore aa_primaryAppleAccount]; |
213 | return primaryAppleAccount.username; | |
214 | } | |
215 | ||
5c19dc3a | 216 | |
d8f41ccd A |
217 | static CFOptionFlags flagsForAsk(Applicant *applicant) |
218 | { | |
5c19dc3a | 219 | return kCFUserNotificationPlainAlertLevel | CFUserNotificationSecureTextField(0); |
d8f41ccd A |
220 | } |
221 | ||
5c19dc3a | 222 | |
d8f41ccd A |
223 | // NOTE: gives precedence to OnScreen |
224 | static Applicant *firstApplicantWaitingOrOnScreen() | |
225 | { | |
226 | Applicant *waiting = nil; | |
227 | for (Applicant *applicant in [applicants objectEnumerator]) { | |
228 | if (applicant.applicantUIState == ApplicantOnScreen) { | |
229 | return applicant; | |
230 | } else if (applicant.applicantUIState == ApplicantWaiting) { | |
231 | waiting = applicant; | |
232 | } | |
233 | } | |
234 | ||
235 | return waiting; | |
236 | } | |
237 | ||
5c19dc3a | 238 | |
d8f41ccd A |
239 | static NSMutableArray *applicantsInState(ApplicantUIState state) |
240 | { | |
241 | NSMutableArray *results = [NSMutableArray new]; | |
242 | for (Applicant *applicant in [applicants objectEnumerator]) { | |
243 | if (applicant.applicantUIState == state) { | |
244 | [results addObject:applicant]; | |
245 | } | |
246 | } | |
247 | ||
248 | return results; | |
249 | } | |
250 | ||
5c19dc3a | 251 | |
fa7225c8 | 252 | BOOL processRequests(CFErrorRef *error) { |
5c19dc3a A |
253 | NSMutableArray *toAccept = [[applicantsInState(ApplicantAccepted) mapWithBlock:^id(id obj) {return (id)[obj rawPeerInfo];}] mutableCopy]; |
254 | NSMutableArray *toReject = [[applicantsInState(ApplicantRejected) mapWithBlock:^id(id obj) {return (id)[obj rawPeerInfo];}] mutableCopy]; | |
255 | bool ok = true; | |
256 | ||
e0e0d90e | 257 | if ([toAccept count]) { |
fa7225c8 | 258 | secnotice("cjr", "Process accept: %@", toAccept); |
5c19dc3a | 259 | ok = ok && SOSCCAcceptApplicants((__bridge CFArrayRef) toAccept, error); |
e0e0d90e | 260 | if (ok) { |
fa7225c8 | 261 | secnotice("cjr", "kSOSCCHoldLockForInitialSync"); |
e0e0d90e A |
262 | notify_post(kSOSCCHoldLockForInitialSync); |
263 | } | |
264 | } | |
5c19dc3a | 265 | |
e0e0d90e | 266 | if ([toReject count]) { |
fa7225c8 | 267 | secnotice("cjr", "Process reject: %@", toReject); |
5c19dc3a | 268 | ok = ok && SOSCCRejectApplicants((__bridge CFArrayRef) toReject, error); |
e0e0d90e | 269 | } |
5c19dc3a A |
270 | |
271 | return ok; | |
d8f41ccd A |
272 | } |
273 | ||
5c19dc3a A |
274 | |
275 | static void cancelCurrentAlert(bool stopRunLoop) { | |
276 | if (currentAlertSource) { | |
277 | CFRunLoopRemoveSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode); | |
278 | CFReleaseNull(currentAlertSource); | |
279 | } | |
280 | if (currentAlert) { | |
281 | CFUserNotificationCancel(currentAlert); | |
282 | CFReleaseNull(currentAlert); | |
283 | } | |
284 | if (stopRunLoop) { | |
285 | CFRunLoopStop(CFRunLoopGetCurrent()); | |
286 | } | |
287 | currentAlertIsForKickOut = currentAlertIsForApplicants = false; | |
d8f41ccd A |
288 | } |
289 | ||
5c19dc3a | 290 | |
d8f41ccd A |
291 | static void askAboutAll(bool passwordFailure); |
292 | ||
5c19dc3a | 293 | |
d8f41ccd A |
294 | static void applicantChoice(CFUserNotificationRef userNotification, CFOptionFlags responseFlags) |
295 | { | |
5c19dc3a A |
296 | ApplicantUIState choice; |
297 | ||
298 | if (kCFUserNotificationAlternateResponse == responseFlags) { | |
299 | choice = ApplicantRejected; | |
300 | } else if (kCFUserNotificationDefaultResponse == responseFlags) { | |
301 | choice = ApplicantAccepted; | |
302 | } else { | |
fa7225c8 | 303 | secnotice("cjr", "Unexpected response %lu", responseFlags); |
5c19dc3a A |
304 | choice = ApplicantRejected; |
305 | } | |
306 | ||
307 | BOOL processed = NO; | |
308 | CFErrorRef error = NULL; | |
309 | NSArray *onScreen = applicantsInState(ApplicantOnScreen); | |
310 | ||
311 | [onScreen enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { | |
312 | Applicant* applicant = (Applicant *) obj; | |
313 | applicant.applicantUIState = choice; | |
314 | }]; | |
315 | ||
316 | if (choice == ApplicantRejected) { | |
317 | // If this device has ever set up the public key this should work without the password... | |
318 | processed = processRequests(&error); | |
319 | if (processed) { | |
fa7225c8 | 320 | secnotice("cjr", "Didn't need password to process %@", onScreen); |
5c19dc3a A |
321 | cancelCurrentAlert(true); |
322 | return; | |
323 | } else { | |
324 | // ...however if the public key gets lost we should "just" fall through to the validate | |
325 | // password path. | |
fa7225c8 A |
326 | secnotice("cjr", "Couldn't process reject without password (e=%@) for %@ (will try with password next)", error, onScreen); |
327 | ||
6b200bc3 | 328 | if(CFErrorIsMalfunctioningKeybagError(error)){ |
fa7225c8 A |
329 | secnotice("cjr", "system is locked, dismiss the notification"); |
330 | processApplicantsAfterUnlock = true; | |
331 | return; | |
332 | } | |
5c19dc3a A |
333 | } |
334 | CFReleaseNull(error); | |
335 | } | |
336 | ||
e0e0d90e | 337 | NSString *password = (__bridge NSString *) CFUserNotificationGetResponseValue(userNotification, kCFUserNotificationTextFieldValuesKey, 0); |
5c19dc3a | 338 | if (!password) { |
fa7225c8 | 339 | secnotice("cjr", "No password given, retry"); |
5c19dc3a A |
340 | askAboutAll(true); |
341 | return; | |
342 | } | |
343 | const char *passwordUTF8 = [password UTF8String]; | |
344 | NSData *passwordBytes = [NSData dataWithBytes:passwordUTF8 length:strlen(passwordUTF8)]; | |
345 | ||
346 | // Sometimes securityd crashes between SOSCCRegisterUserCredentials and processRequests | |
347 | // (which results in a process error -- I think this is 13355140), as a workaround we retry | |
348 | // failure a few times before we give up. | |
349 | for (int try = 0; try < 5 && !processed; try++) { | |
e0e0d90e | 350 | if (!SOSCCTryUserCredentials(CFSTR(""), (__bridge CFDataRef) passwordBytes, &error)) { |
fa7225c8 | 351 | secnotice("cjr", "Try user credentials failed %@", error); |
5c19dc3a A |
352 | if ((error == NULL) || |
353 | (CFEqual(kSOSErrorDomain, CFErrorGetDomain(error)) && kSOSErrorWrongPassword == CFErrorGetCode(error))) { | |
fa7225c8 | 354 | secnotice("cjr", "Calling askAboutAll again..."); |
5c19dc3a | 355 | [onScreen enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { |
e0e0d90e | 356 | Applicant *applicant = (Applicant*) obj; |
5c19dc3a A |
357 | applicant.applicantUIState = ApplicantWaiting; |
358 | }]; | |
359 | askAboutAll(true); | |
360 | CFReleaseNull(error); | |
361 | return; | |
362 | } | |
363 | EXIT_LOGGED_FAILURE(EX_DATAERR); | |
364 | } | |
365 | ||
366 | processed = processRequests(&error); | |
367 | if (!processed) { | |
fa7225c8 | 368 | secnotice("cjr", "Can't processRequests: %@ for %@", error, onScreen); |
5c19dc3a A |
369 | } |
370 | CFReleaseNull(error); | |
371 | } | |
372 | ||
373 | if (processed && firstApplicantWaitingOrOnScreen()) { | |
374 | cancelCurrentAlert(false); | |
375 | askAboutAll(false); | |
376 | } else { | |
377 | cancelCurrentAlert(true); | |
378 | } | |
d8f41ccd A |
379 | } |
380 | ||
5c19dc3a | 381 | |
d8f41ccd A |
382 | static void passwordFailurePrompt() |
383 | { | |
866f8763 A |
384 | #pragma clang diagnostic push |
385 | #pragma clang diagnostic ignored "-Wformat-nonliteral" | |
5c19dc3a | 386 | NSString *pwIncorrect = [NSString stringWithFormat:(NSString *)CFBridgingRelease(SecCopyCKString(SEC_CK_PASSWORD_INCORRECT)), appleIDAccountName()]; |
866f8763 | 387 | #pragma clang diagnostic pop |
5c19dc3a A |
388 | NSString *tryAgain = CFBridgingRelease(SecCopyCKString(SEC_CK_TRY_AGAIN)); |
389 | NSDictionary *noteAttributes = @{ | |
390 | (id) kCFUserNotificationAlertHeaderKey : pwIncorrect, | |
391 | (id) kCFUserNotificationDefaultButtonTitleKey : tryAgain, | |
392 | (id) kCFUserNotificationAlertTopMostKey : @YES, // get us onto the lock screen | |
393 | (__bridge id) SBUserNotificationDontDismissOnUnlock: @YES, | |
394 | (__bridge id) SBUserNotificationDismissOnLock : @NO, | |
395 | }; | |
396 | CFOptionFlags flags = kCFUserNotificationPlainAlertLevel; | |
397 | SInt32 err; | |
e0e0d90e | 398 | CFUserNotificationRef note = CFUserNotificationCreate(NULL, 0.0, flags, &err, (__bridge CFDictionaryRef) noteAttributes); |
5c19dc3a A |
399 | |
400 | if (note) { | |
401 | CFUserNotificationReceiveResponse(note, 0.0, &flags); | |
402 | CFRelease(note); | |
403 | } | |
d8f41ccd A |
404 | } |
405 | ||
5c19dc3a A |
406 | |
407 | static NSString *getLocalizedApprovalBody(NSString *deviceType) { | |
408 | CFStringRef applicationReminder = NULL; | |
409 | ||
410 | if ([deviceType isEqualToString:@"iPhone"]) | |
411 | applicationReminder = SecCopyCKString(SEC_CK_APPROVAL_BODY_IOS_IPHONE); | |
412 | else if ([deviceType isEqualToString:@"iPod"]) | |
413 | applicationReminder = SecCopyCKString(SEC_CK_APPROVAL_BODY_IOS_IPOD); | |
414 | else if ([deviceType isEqualToString:@"iPad"]) | |
415 | applicationReminder = SecCopyCKString(SEC_CK_APPROVAL_BODY_IOS_IPAD); | |
416 | else if ([deviceType isEqualToString:@"Mac"]) | |
417 | applicationReminder = SecCopyCKString(SEC_CK_APPROVAL_BODY_IOS_MAC); | |
418 | else | |
419 | applicationReminder = SecCopyCKString(SEC_CK_APPROVAL_BODY_IOS_GENERIC); | |
420 | ||
421 | return (__bridge_transfer NSString *) applicationReminder; | |
422 | } | |
423 | ||
424 | ||
425 | static NSDictionary *createNote(Applicant *applicantToAskAbout) | |
426 | { | |
427 | if(!applicantToAskAbout || !applicantToAskAbout.name || !applicantToAskAbout.deviceType) | |
428 | return NULL; | |
429 | ||
866f8763 A |
430 | #pragma clang diagnostic push |
431 | #pragma clang diagnostic ignored "-Wformat-nonliteral" | |
432 | NSString *header = [NSString stringWithFormat: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVAL_TITLE), applicantToAskAbout.name]; | |
5c19dc3a | 433 | NSString *body = [NSString stringWithFormat: getLocalizedApprovalBody(applicantToAskAbout.deviceType), appleIDAccountName()]; |
866f8763 | 434 | #pragma clang diagnostic pop |
5c19dc3a A |
435 | |
436 | return @{ | |
437 | (id) kCFUserNotificationAlertHeaderKey : header, | |
438 | (id) kCFUserNotificationAlertMessageKey : body, | |
866f8763 A |
439 | (id) kCFUserNotificationDefaultButtonTitleKey : (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVE), |
440 | (id) kCFUserNotificationAlternateButtonTitleKey: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_DECLINE), | |
5c19dc3a A |
441 | (id) kCFUserNotificationTextFieldTitlesKey : (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_ICLOUD_PASSWORD), |
442 | (id) kCFUserNotificationAlertTopMostKey : @YES, // get us onto the lock screen | |
443 | (__bridge_transfer id) SBUserNotificationDontDismissOnUnlock: @YES, | |
444 | (__bridge_transfer id) SBUserNotificationDismissOnLock : @NO, | |
d8f41ccd A |
445 | }; |
446 | } | |
447 | ||
5c19dc3a | 448 | |
d8f41ccd A |
449 | static void askAboutAll(bool passwordFailure) |
450 | { | |
5c19dc3a | 451 | if ([[MCProfileConnection sharedConnection] effectiveBoolValueForSetting: MCFeatureAccountModificationAllowed] == MCRestrictedBoolExplicitNo) { |
fa7225c8 | 452 | secnotice("cjr", "Account modifications not allowed."); |
5c19dc3a A |
453 | return; |
454 | } | |
455 | ||
456 | if (passwordFailure) { | |
457 | passwordFailurePrompt(); | |
458 | } | |
459 | ||
460 | if ((passwordFailure || !currentAlertIsForApplicants) && currentAlert) { | |
461 | if (!currentAlertIsForApplicants) { | |
462 | CFUserNotificationCancel(currentAlert); | |
463 | } | |
464 | // after password failure we need to remove the existing alert and supporting objects | |
465 | // because we can't reuse them. | |
466 | CFReleaseNull(currentAlert); | |
467 | CFReleaseNull(currentAlertSource); | |
468 | } | |
469 | currentAlertIsForApplicants = true; | |
470 | ||
471 | Applicant *applicantToAskAbout = firstApplicantWaitingOrOnScreen(); | |
fa7225c8 | 472 | secnotice("cjr", "Asking about: %@ (of: %@)", applicantToAskAbout, applicants); |
5c19dc3a A |
473 | |
474 | NSDictionary *noteAttributes = createNote(applicantToAskAbout); | |
475 | if(!noteAttributes) { | |
fa7225c8 | 476 | secnotice("cjr", "NULL data for %@", applicantToAskAbout); |
5c19dc3a A |
477 | cancelCurrentAlert(true); |
478 | return; | |
479 | } | |
480 | ||
481 | CFOptionFlags flags = flagsForAsk(applicantToAskAbout); | |
482 | ||
483 | if (currentAlert) { | |
e0e0d90e | 484 | SInt32 err = CFUserNotificationUpdate(currentAlert, 0, flags, (__bridge CFDictionaryRef) noteAttributes); |
5c19dc3a | 485 | if (err) { |
fa7225c8 | 486 | secnotice("cjr", "CFUserNotificationUpdate err=%d", (int)err); |
5c19dc3a A |
487 | EXIT_LOGGED_FAILURE(EX_SOFTWARE); |
488 | } | |
489 | } else { | |
490 | SInt32 err = 0; | |
e0e0d90e | 491 | currentAlert = CFUserNotificationCreate(NULL, 0.0, flags, &err, (__bridge CFDictionaryRef) noteAttributes); |
5c19dc3a | 492 | if (err) { |
fa7225c8 | 493 | secnotice("cjr", "Can't make notification for %@ err=%x", applicantToAskAbout, (int)err); |
5c19dc3a A |
494 | EXIT_LOGGED_FAILURE(EX_SOFTWARE); |
495 | } | |
496 | ||
497 | currentAlertSource = CFUserNotificationCreateRunLoopSource(NULL, currentAlert, applicantChoice, 0); | |
498 | CFRunLoopAddSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode); | |
499 | } | |
500 | ||
501 | applicantToAskAbout.applicantUIState = ApplicantOnScreen; | |
d8f41ccd A |
502 | } |
503 | ||
5c19dc3a | 504 | |
d8f41ccd A |
505 | static void scheduleActivity(int alertInterval) |
506 | { | |
507 | xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0); | |
508 | xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, alertInterval); | |
509 | xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, XPC_ACTIVITY_INTERVAL_1_MIN); | |
510 | xpc_dictionary_set_bool(options, XPC_ACTIVITY_REPEATING, false); | |
511 | xpc_dictionary_set_bool(options, XPC_ACTIVITY_ALLOW_BATTERY, true); | |
512 | xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY); | |
513 | ||
514 | xpc_activity_register(kLaunchLaterXPCName, options, ^(xpc_activity_t activity) { | |
fa7225c8 | 515 | secnotice("cjr", "activity handler fired"); |
d8f41ccd A |
516 | }); |
517 | } | |
518 | ||
5c19dc3a A |
519 | |
520 | static void reminderChoice(CFUserNotificationRef userNotification, CFOptionFlags responseFlags) { | |
521 | if (responseFlags == kCFUserNotificationAlternateResponse || responseFlags == kCFUserNotificationDefaultResponse) { | |
522 | PersistentState *state = [PersistentState loadFromStorage]; | |
d8f41ccd A |
523 | NSDate *nowish = [NSDate new]; |
524 | state.pendingApplicationReminder = [nowish dateByAddingTimeInterval: state.pendingApplicationReminderAlertInterval]; | |
525 | scheduleActivity(state.pendingApplicationReminderAlertInterval); | |
526 | [state writeToStorage]; | |
5c19dc3a A |
527 | if (responseFlags == kCFUserNotificationAlternateResponse) { |
528 | // Use security code | |
d8f41ccd | 529 | BOOL ok = [[LSApplicationWorkspace defaultWorkspace] openSensitiveURL:[NSURL URLWithString:castleKeychainUrl] withOptions:nil]; |
fa7225c8 | 530 | secnotice("cjr", "%s iCSC: opening %@ ok=%d", __FUNCTION__, castleKeychainUrl, ok); |
d8f41ccd A |
531 | } |
532 | } | |
533 | ||
534 | cancelCurrentAlert(true); | |
535 | } | |
536 | ||
537 | ||
5c19dc3a A |
538 | static bool iCloudResetAvailable() { |
539 | SecureBackup *backupd = [SecureBackup new]; | |
540 | NSDictionary *backupdResults; | |
541 | NSError *error = [backupd getAccountInfoWithInfo:nil results:&backupdResults]; | |
fa7225c8 | 542 | secnotice("cjr", "SecureBackup e=%@ r=%@", error, backupdResults); |
5c19dc3a | 543 | return (error == nil && [backupdResults[kSecureBackupIsEnabledKey] isEqualToNumber:@YES]); |
d8f41ccd A |
544 | } |
545 | ||
5c19dc3a | 546 | |
5c19dc3a A |
547 | static NSString *getLocalizedApplicationReminder() { |
548 | CFStringRef applicationReminder = NULL; | |
549 | switch (MGGetSInt32Answer(kMGQDeviceClassNumber, MGDeviceClassInvalid)) { | |
550 | case MGDeviceClassiPhone: | |
551 | applicationReminder = SecCopyCKString(SEC_CK_REMINDER_BODY_IOS_IPHONE); | |
552 | break; | |
553 | case MGDeviceClassiPod: | |
554 | applicationReminder = SecCopyCKString(SEC_CK_REMINDER_BODY_IOS_IPOD); | |
555 | break; | |
556 | case MGDeviceClassiPad: | |
557 | applicationReminder = SecCopyCKString(SEC_CK_REMINDER_BODY_IOS_IPAD); | |
558 | break; | |
559 | default: | |
560 | applicationReminder = SecCopyCKString(SEC_CK_REMINDER_BODY_IOS_GENERIC); | |
561 | break; | |
562 | } | |
563 | return (__bridge_transfer NSString *) applicationReminder; | |
d8f41ccd A |
564 | } |
565 | ||
7fb2cbd2 A |
566 | static bool isSOSInternalDevice(void) { |
567 | static dispatch_once_t onceToken; | |
568 | static BOOL internal = NO; | |
569 | dispatch_once(&onceToken, ^{ | |
570 | internal = os_variant_has_internal_diagnostics("com.apple.security"); | |
571 | }); | |
572 | return internal; | |
573 | } | |
5c19dc3a A |
574 | |
575 | static void postApplicationReminderAlert(NSDate *nowish, PersistentState *state, unsigned int alertInterval) | |
d8f41ccd | 576 | { |
5c19dc3a A |
577 | NSString *body = getLocalizedApplicationReminder(); |
578 | bool has_iCSC = iCloudResetAvailable(); | |
d8f41ccd | 579 | |
7fb2cbd2 | 580 | if (isSOSInternalDevice() && |
e0e0d90e | 581 | state.defaultPendingApplicationReminderAlertInterval != state.pendingApplicationReminderAlertInterval) { |
fa7225c8 | 582 | #ifdef DEBUG |
5c19dc3a A |
583 | body = [body stringByAppendingFormat: @"〖debug interval %u; wait time %@〗", |
584 | state.pendingApplicationReminderAlertInterval, | |
585 | [nowish copyDescriptionOfIntervalSince:state.applicationDate]]; | |
e3d460c9 | 586 | #endif |
d8f41ccd | 587 | } |
5c19dc3a | 588 | |
d8f41ccd | 589 | NSDictionary *pendingAttributes = @{ |
6b200bc3 | 590 | (id) kCFUserNotificationAlertHeaderKey : CFBridgingRelease(SecCopyCKString(SEC_CK_REMINDER_TITLE_IOS)), |
5c19dc3a | 591 | (id) kCFUserNotificationAlertMessageKey : body, |
6b200bc3 A |
592 | (id) kCFUserNotificationDefaultButtonTitleKey : CFBridgingRelease(SecCopyCKString(SEC_CK_REMINDER_BUTTON_OK)), |
593 | (id) kCFUserNotificationAlternateButtonTitleKey: has_iCSC ? CFBridgingRelease(SecCopyCKString(SEC_CK_REMINDER_BUTTON_ICSC)) : @"", | |
5c19dc3a A |
594 | (id) kCFUserNotificationAlertTopMostKey : @YES, |
595 | (__bridge id) SBUserNotificationDontDismissOnUnlock : @YES, | |
596 | (__bridge id) SBUserNotificationDismissOnLock : @NO, | |
5c19dc3a | 597 | }; |
d8f41ccd | 598 | SInt32 err = 0; |
5c19dc3a A |
599 | currentAlert = CFUserNotificationCreate(NULL, 0.0, kCFUserNotificationPlainAlertLevel, &err, (__bridge CFDictionaryRef) pendingAttributes); |
600 | ||
601 | if (err) { | |
fa7225c8 | 602 | secnotice("cjr", "Can't make pending notification err=%x", (int)err); |
5c19dc3a A |
603 | } else { |
604 | currentAlertIsForApplicants = false; | |
605 | currentAlertSource = CFUserNotificationCreateRunLoopSource(NULL, currentAlert, reminderChoice, 0); | |
606 | CFRunLoopAddSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode); | |
607 | } | |
d8f41ccd A |
608 | } |
609 | ||
5c19dc3a A |
610 | |
611 | static void kickOutChoice(CFUserNotificationRef userNotification, CFOptionFlags responseFlags) { | |
fa7225c8 A |
612 | secnotice("cjr", "kOC %@ %lu", userNotification, responseFlags); |
613 | ||
614 | //default response: continue -> settings pref pane advanced keychain sync page | |
5c19dc3a A |
615 | if (responseFlags == kCFUserNotificationDefaultResponse) { |
616 | // We need to let things unwind to main for the new state to get saved | |
617 | doOnceInMain(^{ | |
866f8763 | 618 | NSURL *url = [NSURL URLWithString: _isAccountICDP ? rejoinICDPUrl : castleKeychainUrl]; |
822b670c | 619 | BOOL ok = [[LSApplicationWorkspace defaultWorkspace] openSensitiveURL:url withOptions:nil]; |
866f8763 | 620 | secnotice("cjr","kickOutChoice account is iCDP: %d", _isAccountICDP); |
fa7225c8 | 621 | secnotice("cjr", "ok=%d opening %@", ok, url); |
5c19dc3a A |
622 | }); |
623 | } | |
fa7225c8 A |
624 | //alternate response: later -> call CD |
625 | else if (responseFlags == kCFUserNotificationAlternateResponse) { | |
626 | // We need to let things unwind to main for the new state to get saved | |
627 | doOnceInMain(^{ | |
866f8763 A |
628 | if(_isAccountICDP){ |
629 | CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init]; | |
fa7225c8 A |
630 | NSError *localError = nil; |
631 | CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair]; | |
b3971512 A |
632 | |
633 | secnotice("followup", "Posting a follow up (for SOS) of type repair"); | |
fa7225c8 A |
634 | [cdpd postFollowUpWithContext:context error:&localError ]; |
635 | if(localError){ | |
636 | secnotice("cjr", "request to CoreCDP to follow up failed: %@", localError); | |
637 | } | |
866f8763 | 638 | else{ |
fa7225c8 | 639 | secnotice("cjr", "CoreCDP handling follow up"); |
866f8763 A |
640 | _hasPostedFollowupAndStillInError = true; |
641 | } | |
fa7225c8 A |
642 | } |
643 | }); | |
644 | ||
645 | } | |
5c19dc3a | 646 | cancelCurrentAlert(true); |
d8f41ccd A |
647 | } |
648 | ||
5c19dc3a A |
649 | |
650 | CFStringRef const CJRAggdDepartureReasonKey = CFSTR("com.apple.security.circlejoinrequested.departurereason"); | |
651 | CFStringRef const CJRAggdNumCircleDevicesKey = CFSTR("com.apple.security.circlejoinrequested.numcircledevices"); | |
652 | ||
d8f41ccd A |
653 | static void postKickedOutAlert(enum DepartureReason reason) |
654 | { | |
5c19dc3a A |
655 | NSString *header = nil; |
656 | NSString *message = nil; | |
657 | ||
658 | // <rdar://problem/20358568> iCDP telemetry: ☂ Statistics on circle reset and drop out events | |
659 | ADClientSetValueForScalarKey(CJRAggdDepartureReasonKey, reason); | |
660 | ||
661 | int64_t num_peers = 0; | |
662 | CFArrayRef peerList = SOSCCCopyPeerPeerInfo(NULL); | |
663 | if (peerList) { | |
664 | num_peers = CFArrayGetCount(peerList); | |
665 | if (num_peers > 99) { | |
666 | // Round down # peers to 2 significant digits | |
667 | int factor; | |
668 | for (factor = 10; num_peers >= 100*factor; factor *= 10) ; | |
669 | num_peers = (num_peers / factor) * factor; | |
670 | } | |
671 | CFRelease(peerList); | |
672 | } | |
673 | ADClientSetValueForScalarKey(CJRAggdNumCircleDevicesKey, num_peers); | |
674 | ||
675 | debugState = @"pKOA A"; | |
fa7225c8 | 676 | secnotice("cjr", "DepartureReason %d", reason); |
d8f41ccd | 677 | switch (reason) { |
5c19dc3a A |
678 | case kSOSDiscoveredRetirement: |
679 | case kSOSLostPrivateKey: | |
680 | case kSOSWithdrewMembership: | |
681 | // Was: SEC_CK_CR_BODY_WITHDREW | |
682 | // "... if you turn off a switch you have some idea why the light is off" - Murf | |
683 | return; | |
5c19dc3a A |
684 | |
685 | case kSOSNeverAppliedToCircle: | |
686 | // We didn't get kicked out, we were never here. This should only happen if we changed iCloud accounts | |
687 | // (and we had sync on in the previous one, and never had it on in the new one). As this is explicit | |
688 | // user action alot of the "Light switch" argument (above) applies. | |
689 | return; | |
5c19dc3a | 690 | |
fa7225c8 | 691 | case kSOSPasswordChanged: |
5c19dc3a A |
692 | case kSOSNeverLeftCircle: |
693 | case kSOSMembershipRevoked: | |
694 | case kSOSLeftUntrustedCircle: | |
695 | default: | |
696 | header = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE); | |
697 | message = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_IOS); | |
698 | break; | |
d8f41ccd | 699 | } |
5c19dc3a | 700 | |
7fb2cbd2 | 701 | if (isSOSInternalDevice()) { |
5c19dc3a A |
702 | static const char *departureReasonStrings[] = { |
703 | "kSOSDepartureReasonError", | |
704 | "kSOSNeverLeftCircle", | |
705 | "kSOSWithdrewMembership", | |
706 | "kSOSMembershipRevoked", | |
707 | "kSOSLeftUntrustedCircle", | |
708 | "kSOSNeverAppliedToCircle", | |
709 | "kSOSDiscoveredRetirement", | |
710 | "kSOSLostPrivateKey", | |
fa7225c8 | 711 | "kSOSPasswordChanged", |
5c19dc3a A |
712 | "unknown reason" |
713 | }; | |
714 | int idx = (kSOSDepartureReasonError <= reason && reason <= kSOSLostPrivateKey) ? reason : (kSOSLostPrivateKey + 1); | |
866f8763 A |
715 | #pragma clang diagnostic push |
716 | #pragma clang diagnostic ignored "-Wformat-nonliteral" | |
5c19dc3a A |
717 | NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), |
718 | departureReasonStrings[idx]]; | |
866f8763 | 719 | #pragma clang diagnostic pop |
5c19dc3a A |
720 | message = [message stringByAppendingString: reason_str]; |
721 | } | |
722 | ||
d8f41ccd | 723 | NSDictionary *kickedAttributes = @{ |
5c19dc3a A |
724 | (id) kCFUserNotificationAlertHeaderKey : header, |
725 | (id) kCFUserNotificationAlertMessageKey : message, | |
726 | (id) kCFUserNotificationDefaultButtonTitleKey : (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE), | |
727 | (id) kCFUserNotificationAlternateButtonTitleKey: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW), | |
728 | (id) kCFUserNotificationAlertTopMostKey : @YES, | |
729 | (__bridge id) SBUserNotificationDismissOnLock : @NO, | |
730 | (__bridge id) SBUserNotificationDontDismissOnUnlock : @YES, | |
5c19dc3a | 731 | }; |
d8f41ccd | 732 | SInt32 err = 0; |
fa7225c8 | 733 | |
d8f41ccd | 734 | if (currentAlertIsForKickOut) { |
5c19dc3a | 735 | debugState = @"pKOA B"; |
fa7225c8 | 736 | secnotice("cjr", "Updating existing alert %@ with %@", currentAlert, kickedAttributes); |
5c19dc3a | 737 | CFUserNotificationUpdate(currentAlert, 0, kCFUserNotificationPlainAlertLevel, (__bridge CFDictionaryRef) kickedAttributes); |
d8f41ccd | 738 | } else { |
5c19dc3a | 739 | debugState = @"pKOA C"; |
d8f41ccd | 740 | |
5c19dc3a A |
741 | CFUserNotificationRef note = CFUserNotificationCreate(NULL, 0.0, kCFUserNotificationPlainAlertLevel, &err, (__bridge CFDictionaryRef) kickedAttributes); |
742 | assert((note == NULL) == (err != 0)); | |
743 | if (err) { | |
fa7225c8 A |
744 | secnotice("cjr", "Can't make kicked out notification err=%x", (int)err); |
745 | CFReleaseNull(note); | |
5c19dc3a | 746 | } else { |
d8f41ccd A |
747 | currentAlertIsForApplicants = false; |
748 | currentAlertIsForKickOut = true; | |
749 | ||
750 | currentAlert = note; | |
fa7225c8 | 751 | secnotice("cjr", "New ko alert %@ a=%@", currentAlert, kickedAttributes); |
d8f41ccd A |
752 | currentAlertSource = CFUserNotificationCreateRunLoopSource(NULL, currentAlert, kickOutChoice, 0); |
753 | CFRunLoopAddSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode); | |
754 | int backupStateChangeToken; | |
755 | notify_register_dispatch("com.apple.EscrowSecurityAlert.reset", &backupStateChangeToken, dispatch_get_main_queue(), ^(int token) { | |
756 | if (currentAlert == note) { | |
fa7225c8 | 757 | secnotice("cjr", "Backup state might have changed (dS=%@)", debugState); |
d8f41ccd A |
758 | postKickedOutAlert(reason); |
759 | } else { | |
fa7225c8 | 760 | secnotice("cjr", "Backup state may have changed, but we don't care anymore (dS=%@)", debugState); |
d8f41ccd A |
761 | } |
762 | }); | |
fa7225c8 | 763 | |
5c19dc3a | 764 | debugState = @"pKOA D"; |
d8f41ccd | 765 | CFRunLoopRun(); |
5c19dc3a | 766 | debugState = @"pKOA E"; |
d8f41ccd A |
767 | notify_cancel(backupStateChangeToken); |
768 | } | |
769 | } | |
770 | debugState = @"pKOA Z"; | |
771 | } | |
772 | ||
7fb2cbd2 A |
773 | static void askForCDPFollowup() { |
774 | doOnceInMain(^{ | |
775 | NSError *localError = nil; | |
776 | CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init]; | |
777 | CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair]; | |
b3971512 A |
778 | |
779 | secnotice("followup", "Posting a follow up (for SOS) of type repair"); | |
7fb2cbd2 A |
780 | [cdpd postFollowUpWithContext:context error:&localError ]; |
781 | if(localError){ | |
782 | secnotice("cjr", "request to CoreCDP to follow up failed: %@", localError); | |
783 | } | |
784 | else{ | |
785 | secnotice("cjr", "CoreCDP handling follow up"); | |
786 | _hasPostedFollowupAndStillInError = true; | |
787 | } | |
788 | }); | |
789 | } | |
790 | ||
d8f41ccd A |
791 | static bool processEvents() |
792 | { | |
5c19dc3a A |
793 | debugState = @"processEvents A"; |
794 | ||
b54c578e A |
795 | CFErrorRef error = NULL; |
796 | CFErrorRef departError = NULL; | |
797 | SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircleNonCached(&error); | |
8a50f688 A |
798 | enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&departError); |
799 | ||
b54c578e A |
800 | BOOL abortFromError = isErrorFromXPC(error); |
801 | if(abortFromError && circleStatus == kSOSCCError) { | |
802 | secnotice("cjr", "returning from processEvents due to error returned from securityd: %@", error); | |
8a50f688 | 803 | return true; |
b54c578e A |
804 | } |
805 | if (departureReason == kSOSDepartureReasonError && departError && (CFEqual(sSecXPCErrorDomain, CFErrorGetDomain(departError)))) { | |
8a50f688 A |
806 | secnotice("cjr", "XPC error while checking last departure reason: \"%@\", not processing events", departError); |
807 | return true; | |
808 | } | |
b54c578e A |
809 | |
810 | NSDate *nowish = [NSDate date]; | |
811 | PersistentState *state = [PersistentState loadFromStorage]; | |
812 | secnotice("cjr", "CircleStatus %d -> %d{%d} (s=%p)", state.lastCircleStatus, circleStatus, departureReason, state); | |
8a50f688 | 813 | |
b54c578e | 814 | // Pending application reminder |
5c19dc3a | 815 | NSTimeInterval timeUntilApplicationAlert = [state.pendingApplicationReminder timeIntervalSinceDate:nowish]; |
fa7225c8 | 816 | secnotice("cjr", "Time until pendingApplicationReminder (%@) %f", [state.pendingApplicationReminder debugDescription], timeUntilApplicationAlert); |
5c19dc3a A |
817 | if (circleStatus == kSOSCCRequestPending) { |
818 | if (timeUntilApplicationAlert <= 0) { | |
819 | debugState = @"reminderAlert"; | |
820 | postApplicationReminderAlert(nowish, state, state.pendingApplicationReminderAlertInterval); | |
821 | } else { | |
822 | scheduleActivity(ceil(timeUntilApplicationAlert)); | |
823 | } | |
824 | } | |
866f8763 A |
825 | |
826 | PSKeychainSyncIsUsingICDP(); | |
b54c578e A |
827 | |
828 | // Refresh because sometimes we're fixed elsewhere before we get here. | |
829 | CFReleaseNull(error); | |
830 | circleStatus = SOSCCThisDeviceIsInCircleNonCached(&error); | |
831 | abortFromError = isErrorFromXPC(error); | |
832 | if(abortFromError && circleStatus == kSOSCCError) { | |
833 | secnotice("cjr", "returning from processEvents due to error returned from securityd: %@", error); | |
834 | return true; | |
835 | } | |
836 | ||
866f8763 | 837 | if(_isAccountICDP){ |
7fb2cbd2 A |
838 | state.lastCircleStatus = circleStatus; |
839 | [state writeToStorage]; | |
840 | if(_hasPostedFollowupAndStillInError == true) { | |
841 | secnotice("cjr", "followup not resolved"); | |
842 | _executeProcessEventsOnce = true; | |
843 | return false; | |
844 | } | |
845 | ||
846 | switch(circleStatus) { | |
847 | case kSOSCCInCircle: | |
848 | secnotice("cjr", "follow up should be resolved"); | |
849 | _executeProcessEventsOnce = true; | |
850 | _hasPostedFollowupAndStillInError = false; | |
851 | break; | |
852 | case kSOSCCError: | |
853 | secnotice("cjr", "error from SOSCCThisDeviceIsInCircle: %@", error); | |
854 | askForCDPFollowup(); | |
855 | _executeProcessEventsOnce = true; | |
856 | return false; | |
857 | case kSOSCCCircleAbsent: | |
858 | case kSOSCCNotInCircle: | |
b54c578e | 859 | /* |
7fb2cbd2 A |
860 | You would think we could count on not being iCDP if the account was signed out. Evidently that's wrong. |
861 | So we'll go based on the artifact that when the account object is reset (like by signing out) the | |
862 | departureReason will be set to kSOSDepartureReasonError. So we won't push to get back into a circle if that's | |
863 | the current reason. I've checked code for other ways we could be out. If we boot and can't load the account | |
864 | we'll end up with kSOSDepartureReasonError. Then too if we end up in kSOSDepartureReasonError and reboot we end up | |
865 | in the same place. Leave it to cdpd to decide whether the user needs to sign in to an account. | |
b54c578e A |
866 | */ |
867 | if(departureReason != kSOSDepartureReasonError) { | |
868 | secnotice("cjr", "iCDP: We need to get back into the circle"); | |
7fb2cbd2 | 869 | askForCDPFollowup(); |
b54c578e A |
870 | } else { |
871 | secnotice("cjr", "iCDP: We appear to not be associated with an iCloud account"); | |
872 | } | |
ecaf5866 | 873 | _executeProcessEventsOnce = true; |
866f8763 | 874 | return false; |
7fb2cbd2 A |
875 | case kSOSCCRequestPending: |
876 | break; | |
877 | default: | |
878 | secnotice("cjr", "Unknown circle status %d", circleStatus); | |
866f8763 A |
879 | return false; |
880 | } | |
b54c578e | 881 | } else if(circleStatus == kSOSCCError && state.lastCircleStatus != kSOSCCError && (departureReason == kSOSNeverLeftCircle)) { |
866f8763 | 882 | secnotice("cjr", "SA: error from SOSCCThisDeviceIsInCircle: %@", error); |
fa7225c8 A |
883 | CFIndex errorCode = CFErrorGetCode(error); |
884 | if(errorCode == kSOSErrorPublicKeyAbsent){ | |
866f8763 | 885 | secnotice("cjr", "SA: We need the password to re-validate ourselves - it's changed on another device"); |
fa7225c8 A |
886 | postKickedOutAlert(kSOSPasswordChanged); |
887 | state.lastCircleStatus = kSOSCCError; | |
888 | [state writeToStorage]; | |
889 | return true; | |
890 | } | |
891 | } | |
5c19dc3a A |
892 | // No longer in circle? |
893 | if ((state.lastCircleStatus == kSOSCCInCircle && (circleStatus == kSOSCCNotInCircle || circleStatus == kSOSCCCircleAbsent)) || | |
894 | (state.lastCircleStatus == kSOSCCCircleAbsent && circleStatus == kSOSCCNotInCircle && state.absentCircleWithNoReason) || | |
895 | state.debugShowLeftReason) { | |
896 | // Used to be in the circle, now we aren't - tell the user why | |
897 | debugState = @"processEvents B"; | |
fa7225c8 A |
898 | |
899 | if (state.debugShowLeftReason) { | |
900 | secnotice("cjr", "debugShowLeftReason: %@", state.debugShowLeftReason); | |
5c19dc3a A |
901 | departureReason = [state.debugShowLeftReason intValue]; |
902 | state.debugShowLeftReason = nil; | |
903 | CFReleaseNull(departError); | |
904 | [state writeToStorage]; | |
905 | } | |
906 | ||
907 | if (departureReason != kSOSDepartureReasonError) { | |
908 | state.absentCircleWithNoReason = (circleStatus == kSOSCCCircleAbsent && departureReason == kSOSNeverLeftCircle); | |
fa7225c8 | 909 | secnotice("cjr", "Depature reason %d", departureReason); |
866f8763 A |
910 | if(!_isAccountICDP){ |
911 | secnotice("cjr", "posting revocation notification!"); | |
912 | postKickedOutAlert(departureReason); | |
913 | } | |
914 | else if(_isAccountICDP && _hasPostedFollowupAndStillInError == false){ | |
915 | NSError *localError = nil; | |
916 | CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init]; | |
917 | CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair]; | |
b3971512 A |
918 | |
919 | secnotice("followup", "Posting a follow up (for SOS) of type repair"); | |
866f8763 A |
920 | [cdpd postFollowUpWithContext:context error:&localError ]; |
921 | if(localError){ | |
922 | secnotice("cjr", "request to CoreCDP to follow up failed: %@", localError); | |
923 | } | |
924 | else{ | |
925 | secnotice("cjr", "CoreCDP handling follow up"); | |
926 | _hasPostedFollowupAndStillInError = true; | |
927 | } | |
928 | } | |
929 | else{ | |
930 | secnotice("cjr", "still waiting for followup to resolve"); | |
931 | } | |
fa7225c8 | 932 | secnotice("cjr", "pKOA returned (cS %d lCS %d)", circleStatus, state.lastCircleStatus); |
5c19dc3a | 933 | } else { |
fa7225c8 | 934 | secnotice("cjr", "Couldn't get last departure reason: %@", departError); |
5c19dc3a | 935 | } |
fa7225c8 A |
936 | |
937 | } | |
5c19dc3a A |
938 | |
939 | // Circle applications: pending request(s) started / completed | |
940 | debugState = @"processEvents C"; | |
941 | if (circleStatus != state.lastCircleStatus) { | |
942 | SOSCCStatus lastCircleStatus = state.lastCircleStatus; | |
943 | state.lastCircleStatus = circleStatus; | |
944 | ||
945 | if (lastCircleStatus != kSOSCCRequestPending && circleStatus == kSOSCCRequestPending) { | |
fa7225c8 | 946 | secnotice("cjr", "Pending request started"); |
5c19dc3a A |
947 | state.applicationDate = nowish; |
948 | state.pendingApplicationReminder = [nowish dateByAddingTimeInterval: state.pendingApplicationReminderAlertInterval]; | |
949 | scheduleActivity(state.pendingApplicationReminderAlertInterval); | |
950 | } | |
951 | if (lastCircleStatus == kSOSCCRequestPending && circleStatus != kSOSCCRequestPending) { | |
fa7225c8 | 952 | secnotice("cjr", "Pending request completed"); |
5c19dc3a A |
953 | state.applicationDate = [NSDate distantPast]; |
954 | state.pendingApplicationReminder = [NSDate distantFuture]; | |
955 | } | |
956 | ||
957 | [state writeToStorage]; | |
958 | } | |
959 | ||
960 | if (circleStatus != kSOSCCInCircle) { | |
961 | if (circleStatus == kSOSCCRequestPending && currentAlert) { | |
962 | int notifyToken = 0; | |
963 | CFUserNotificationRef postedAlert = currentAlert; | |
964 | ||
965 | debugState = @"processEvents D1"; | |
966 | notify_register_dispatch(kSOSCCCircleChangedNotification, ¬ifyToken, dispatch_get_main_queue(), ^(int token) { | |
967 | if (postedAlert != currentAlert) { | |
fa7225c8 | 968 | secnotice("cjr", "-- CC after original alert gone (currentAlertIsForApplicants %d, pA %p, cA %p -- %@)", |
b54c578e A |
969 | currentAlertIsForApplicants, postedAlert, currentAlert, currentAlert); |
970 | notify_cancel(token); | |
971 | } else { | |
fa7225c8 | 972 | CFErrorRef localError = NULL; |
b54c578e A |
973 | SOSCCStatus newCircleStatus = SOSCCThisDeviceIsInCircle(&localError); |
974 | BOOL xpcError = isErrorFromXPC(localError); | |
975 | if(xpcError && newCircleStatus == kSOSCCError) { | |
976 | secnotice("cjr", "returning from processEvents due to error returned from securityd: %@", localError); | |
977 | return; | |
978 | } | |
979 | ||
980 | if (newCircleStatus != kSOSCCRequestPending) { | |
981 | if (newCircleStatus == kSOSCCError) | |
982 | secnotice("cjr", "No longer pending (nCS=%d, alert=%@) error: %@", newCircleStatus, currentAlert, localError); | |
983 | else | |
984 | secnotice("cjr", "No longer pending (nCS=%d, alert=%@)", newCircleStatus, currentAlert); | |
5c19dc3a A |
985 | cancelCurrentAlert(true); |
986 | } else { | |
fa7225c8 | 987 | secnotice("cjr", "Still pending..."); |
5c19dc3a A |
988 | } |
989 | CFReleaseNull(localError); | |
990 | } | |
991 | }); | |
992 | debugState = @"processEvents D2"; | |
fa7225c8 | 993 | secnotice("cjr", "NOTE: currentAlertIsForApplicants %d, token %d", currentAlertIsForApplicants, notifyToken); |
5c19dc3a A |
994 | CFRunLoopRun(); |
995 | return true; | |
996 | } | |
fa7225c8 A |
997 | debugState = @"processEvents D4"; |
998 | secnotice("cjr", "SOSCCThisDeviceIsInCircle status %d, not checking applicants", circleStatus); | |
5c19dc3a A |
999 | return false; |
1000 | } | |
1001 | ||
1002 | ||
1003 | // Applicants | |
1004 | debugState = @"processEvents E"; | |
1005 | applicants = [NSMutableDictionary new]; | |
1006 | for (id applicantInfo in (__bridge_transfer NSArray *) SOSCCCopyApplicantPeerInfo(&error)) { | |
1007 | Applicant *applicant = [[Applicant alloc] initWithPeerInfo:(__bridge SOSPeerInfoRef) applicantInfo]; | |
1008 | applicants[applicant.idString] = applicant; | |
1009 | } | |
1010 | ||
1011 | // Log error from SOSCCCopyApplicantPeerInfo() above? | |
1012 | CFReleaseNull(error); | |
1013 | ||
1014 | int notify_token = -42; | |
1015 | debugState = @"processEvents F"; | |
1016 | int notify_register_status = notify_register_dispatch(kSOSCCCircleChangedNotification, ¬ify_token, dispatch_get_main_queue(), ^(int token) { | |
fa7225c8 | 1017 | secnotice("cjr", "Notified: %s", kSOSCCCircleChangedNotification); |
5c19dc3a A |
1018 | CFErrorRef circleStatusError = NULL; |
1019 | ||
1020 | bool needsUpdate = false; | |
1021 | CFErrorRef copyPeerError = NULL; | |
1022 | NSMutableSet *newIds = [NSMutableSet new]; | |
1023 | for (id applicantInfo in (__bridge_transfer NSArray *) SOSCCCopyApplicantPeerInfo(©PeerError)) { | |
1024 | Applicant *newApplicant = [[Applicant alloc] initWithPeerInfo:(__bridge SOSPeerInfoRef) applicantInfo]; | |
1025 | [newIds addObject:newApplicant.idString]; | |
1026 | Applicant *existingApplicant = applicants[newApplicant.idString]; | |
1027 | if (existingApplicant) { | |
1028 | switch (existingApplicant.applicantUIState) { | |
1029 | case ApplicantWaiting: | |
1030 | applicants[newApplicant.idString] = newApplicant; | |
1031 | break; | |
1032 | ||
1033 | case ApplicantOnScreen: | |
1034 | newApplicant.applicantUIState = ApplicantOnScreen; | |
1035 | applicants[newApplicant.idString] = newApplicant; | |
1036 | break; | |
1037 | ||
1038 | default: | |
fa7225c8 | 1039 | secnotice("cjr", "Update to %@ >> %@ with pending order, should work out ok though", existingApplicant, newApplicant); |
5c19dc3a A |
1040 | break; |
1041 | } | |
1042 | } else { | |
1043 | needsUpdate = true; | |
1044 | applicants[newApplicant.idString] = newApplicant; | |
1045 | } | |
1046 | } | |
1047 | if (copyPeerError) { | |
fa7225c8 | 1048 | secnotice("cjr", "Could not update peer info array: %@", copyPeerError); |
5c19dc3a A |
1049 | CFRelease(copyPeerError); |
1050 | return; | |
1051 | } | |
1052 | ||
1053 | NSMutableArray *idsToRemoveFromApplicants = [NSMutableArray new]; | |
1054 | for (NSString *exisitngId in [applicants keyEnumerator]) { | |
1055 | if (![newIds containsObject:exisitngId]) { | |
1056 | [idsToRemoveFromApplicants addObject:exisitngId]; | |
1057 | needsUpdate = true; | |
1058 | } | |
1059 | } | |
1060 | [applicants removeObjectsForKeys:idsToRemoveFromApplicants]; | |
1061 | ||
1062 | if (newIds.count == 0) { | |
fa7225c8 | 1063 | secnotice("cjr", "All applicants were handled elsewhere"); |
5c19dc3a | 1064 | cancelCurrentAlert(true); |
b54c578e A |
1065 | } |
1066 | CFErrorRef circleError = NULL; | |
1067 | SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(&circleError); | |
1068 | BOOL xpcError = isErrorFromXPC(circleError); | |
1069 | if(xpcError && currentCircleStatus == kSOSCCError) { | |
1070 | secnotice("cjr", "returning early due to error returned from securityd: %@", circleError); | |
1071 | return; | |
1072 | } | |
1073 | if (kSOSCCInCircle != currentCircleStatus) { | |
1074 | secnotice("cjr", "Left circle (%d), not handling remaining %lu applicants", currentCircleStatus, (unsigned long)newIds.count); | |
5c19dc3a A |
1075 | cancelCurrentAlert(true); |
1076 | } | |
1077 | if (needsUpdate) { | |
1078 | askAboutAll(false); | |
1079 | } else { | |
fa7225c8 | 1080 | secnotice("cjr", "needsUpdate false, not updating alert"); |
5c19dc3a A |
1081 | } |
1082 | // Log circleStatusError? | |
1083 | CFReleaseNull(circleStatusError); | |
1084 | }); | |
fa7225c8 | 1085 | secnotice("cjr", "ACC token %d, status %d", notify_token, notify_register_status); |
5c19dc3a A |
1086 | debugState = @"processEvents F2"; |
1087 | ||
1088 | if (applicants.count == 0) { | |
fa7225c8 | 1089 | secnotice("cjr", "No applicants"); |
5c19dc3a A |
1090 | } else { |
1091 | debugState = @"processEvents F3"; | |
1092 | askAboutAll(false); | |
1093 | debugState = @"processEvents F4"; | |
1094 | if (currentAlert) { | |
1095 | debugState = @"processEvents F5"; | |
1096 | CFRunLoopRun(); | |
1097 | } | |
1098 | } | |
1099 | ||
1100 | debugState = @"processEvents F6"; | |
1101 | notify_cancel(notify_token); | |
1102 | debugState = @"processEvents DONE"; | |
1103 | ||
1104 | return false; | |
d8f41ccd A |
1105 | } |
1106 | ||
5c19dc3a A |
1107 | |
1108 | int main (int argc, const char * argv[]) { | |
b54c578e A |
1109 | os_transaction_t txion = os_transaction_create("com.apple.security.circle-join-requested"); |
1110 | ||
5c19dc3a | 1111 | @autoreleasepool { |
5c19dc3a | 1112 | |
fa7225c8 A |
1113 | // NOTE: DISPATCH_QUEUE_PRIORITY_LOW will not actually manage to drain events in a lot of cases (like circleStatus != kSOSCCInCircle) |
1114 | xpc_set_event_stream_handler("com.apple.notifyd.matching", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(xpc_object_t object) { | |
1115 | char *event_description = xpc_copy_description(object); | |
1116 | const char *notificationName = xpc_dictionary_get_string(object, "Notification"); | |
1117 | ||
1118 | if (notificationName && strcmp(notificationName, kUserKeybagStateChangeNotification)==0) { | |
1119 | secnotice("cjr", "keybag changed!"); | |
1120 | keybagStateChange(); | |
1121 | } | |
1122 | ||
1123 | secnotice("cjr", "notifyd event: %s\nAlert (%p) %s %s\ndebugState: %@", event_description, currentAlert, | |
1124 | currentAlertIsForApplicants ? "for applicants" : "!applicants", | |
1125 | currentAlertIsForKickOut ? "KO" : "!KO", debugState); | |
1126 | free(event_description); | |
1127 | }); | |
1128 | ||
1129 | xpc_activity_register(kLaunchLaterXPCName, XPC_ACTIVITY_CHECK_IN, ^(xpc_activity_t activity) { | |
1130 | }); | |
1131 | ||
5c19dc3a | 1132 | int falseInARow = 0; |
ecaf5866 | 1133 | while (falseInARow < 2 && !_executeProcessEventsOnce) { |
5c19dc3a | 1134 | if (processEvents()) { |
fa7225c8 | 1135 | secnotice("cjr", "Processed events!!!"); |
5c19dc3a A |
1136 | falseInARow = 0; |
1137 | } else { | |
1138 | falseInARow++; | |
1139 | } | |
1140 | cancelCurrentAlert(false); | |
1141 | if (doOnceInMainBlockChain) { | |
1142 | doOnceInMainBlockChain(); | |
1143 | doOnceInMainBlockChain = NULL; | |
1144 | } | |
1145 | } | |
d8f41ccd | 1146 | } |
fa7225c8 A |
1147 | |
1148 | secnotice("cjr", "Done"); | |
b54c578e A |
1149 | (void) txion; // But we really do want this around, compiler... |
1150 | txion = nil; | |
5c19dc3a | 1151 | return(0); |
d8f41ccd | 1152 | } |