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