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