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