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