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