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