]> git.saurik.com Git - apple/security.git/blame - CircleJoinRequested/CircleJoinRequested.m
Security-59754.80.3.tar.gz
[apple/security.git] / CircleJoinRequested / CircleJoinRequested.m
CommitLineData
866f8763
A
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
d8f41ccd
A
24#import <Accounts/Accounts.h>
25#import <Accounts/ACAccountStore_Private.h>
822b670c 26#import <Accounts/ACAccountType_Private.h>
5c19dc3a 27#import <AggregateDictionary/ADClient.h>
822b670c 28#import <AppSupport/AppSupportUtils.h>
d8f41ccd 29#import <AppleAccount/AppleAccount.h>
d8f41ccd 30#import <AppleAccount/ACAccountStore+AppleAccount.h>
822b670c
A
31#import <CloudServices/SecureBackup.h>
32#import <CoreFoundation/CFUserNotification.h>
d8f41ccd 33#import <Foundation/Foundation.h>
d64be36e 34#import <ManagedConfiguration/ManagedConfiguration.h>
822b670c
A
35#import <MobileCoreServices/MobileCoreServices.h>
36#import <MobileCoreServices/LSApplicationWorkspace.h>
37#import <MobileGestalt.h>
b54c578e 38#import <os/transaction_private.h>
822b670c
A
39#import <ProtectedCloudStorage/CloudIdentity.h>
40#import <Security/SecFrameworkStrings.h>
41#import <SpringBoardServices/SBSCFUserNotificationKeys.h>
d8f41ccd 42#include <dispatch/dispatch.h>
b54c578e
A
43#include "keychain/SecureObjectSync/SOSCloudCircle.h"
44#include "keychain/SecureObjectSync/SOSCloudCircleInternal.h"
45#include "keychain/SecureObjectSync/SOSPeerInfo.h"
46#include "keychain/SecureObjectSync/SOSInternal.h"
d8f41ccd
A
47#include <notify.h>
48#include <sysexits.h>
49#import "Applicant.h"
50#import "NSArray+map.h"
5c19dc3a 51#import "PersistentState.h"
d8f41ccd
A
52#include <xpc/private.h>
53#include <sys/time.h>
54#import "NSDate+TimeIntervalDescription.h"
d8f41ccd
A
55#include <xpc/activity.h>
56#include <xpc/private.h>
fa7225c8 57#import "os/activity.h"
d8f41ccd 58#import <syslog.h>
5c19dc3a
A
59#include "utilities/SecCFRelease.h"
60#include "utilities/debugging.h"
fa7225c8 61#include "utilities/SecAKSWrappers.h"
6b200bc3 62#include "utilities/SecCFWrappers.h"
8a50f688 63#include <utilities/SecXPCError.h>
7fb2cbd2 64#import <os/variant_private.h>
fa7225c8
A
65
66#import "CoreCDP/CDPFollowUpController.h"
67#import "CoreCDP/CDPFollowUpContext.h"
866f8763 68#import <CoreCDP/CDPAccount.h>
d8f41ccd
A
69
70// As long as we are logging the failure use exit code of zero to make launchd happy
b54c578e 71#define EXIT_LOGGED_FAILURE(code) exit(0)
d8f41ccd
A
72
73const char *kLaunchLaterXPCName = "com.apple.security.CircleJoinRequestedTick";
74CFRunLoopSourceRef currentAlertSource = NULL;
75CFUserNotificationRef currentAlert = NULL;
76bool currentAlertIsForApplicants = true;
77bool currentAlertIsForKickOut = false;
78NSMutableDictionary *applicants = nil;
79volatile NSString *debugState = @"main?";
80dispatch_block_t doOnceInMainBlockChain = NULL;
fa7225c8
A
81bool _isLocked = true;
82bool processApplicantsAfterUnlock = false;
83bool _unlockedSinceBoot = false;
866f8763
A
84bool _hasPostedFollowupAndStillInError = false;
85bool _isAccountICDP = false;
ecaf5866 86bool _executeProcessEventsOnce = false;
d8f41ccd 87
866f8763
A
88NSString *castleKeychainUrl = @"prefs:root=APPLE_ACCOUNT&path=ICLOUD_SERVICE/com.apple.Dataclass.KeychainSync/ADVANCED";
89NSString *rejoinICDPUrl = @"prefs:root=APPLE_ACCOUNT&aaaction=CDP&command=rejoin";
d8f41ccd 90
fa7225c8
A
91BOOL processRequests(CFErrorRef *error);
92
b54c578e
A
93static 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
866f8763
A
103static 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}
fa7225c8
A
119
120static void keybagDidLock()
121{
122 secnotice("cjr", "keybagDidLock");
123}
124
125static void keybagDidUnlock()
126{
127 secnotice("cjr", "keybagDidUnlock");
128
129 CFErrorRef error = NULL;
b54c578e 130
fa7225c8
A
131 if(processApplicantsAfterUnlock){
132 processRequests(&error);
133 processApplicantsAfterUnlock = false;
134 }
866f8763
A
135
136 SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircle(&error);
b54c578e
A
137 BOOL xpcError = isErrorFromXPC(error);
138 if(xpcError && circleStatus == kSOSCCError) {
139 secnotice("cjr", "returning early due to error returned from securityd: %@", error);
8a50f688
A
140 return;
141 }
142 else if (_isAccountICDP && (circleStatus == kSOSCCError || circleStatus == kSOSCCCircleAbsent || circleStatus == kSOSCCNotInCircle) && _hasPostedFollowupAndStillInError == false) {
866f8763
A
143 NSError *localError = nil;
144 CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair];
145 CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init];
b3971512
A
146
147 secnotice("followup", "Posting a follow up (for SOS) of type repair");
866f8763
A
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 }
fa7225c8
A
164}
165
166static 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
180static 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
d8f41ccd
A
196static 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
5c19dc3a 208
d8f41ccd
A
209static NSString *appleIDAccountName()
210{
5c19dc3a 211 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
d8f41ccd
A
212 ACAccount *primaryAppleAccount = [accountStore aa_primaryAppleAccount];
213 return primaryAppleAccount.username;
214}
215
5c19dc3a 216
d8f41ccd
A
217static CFOptionFlags flagsForAsk(Applicant *applicant)
218{
5c19dc3a 219 return kCFUserNotificationPlainAlertLevel | CFUserNotificationSecureTextField(0);
d8f41ccd
A
220}
221
5c19dc3a 222
d8f41ccd
A
223// NOTE: gives precedence to OnScreen
224static 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
5c19dc3a 238
d8f41ccd
A
239static 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
5c19dc3a 251
fa7225c8 252BOOL processRequests(CFErrorRef *error) {
5c19dc3a
A
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
e0e0d90e 257 if ([toAccept count]) {
fa7225c8 258 secnotice("cjr", "Process accept: %@", toAccept);
5c19dc3a 259 ok = ok && SOSCCAcceptApplicants((__bridge CFArrayRef) toAccept, error);
e0e0d90e 260 if (ok) {
fa7225c8 261 secnotice("cjr", "kSOSCCHoldLockForInitialSync");
e0e0d90e
A
262 notify_post(kSOSCCHoldLockForInitialSync);
263 }
264 }
5c19dc3a 265
e0e0d90e 266 if ([toReject count]) {
fa7225c8 267 secnotice("cjr", "Process reject: %@", toReject);
5c19dc3a 268 ok = ok && SOSCCRejectApplicants((__bridge CFArrayRef) toReject, error);
e0e0d90e 269 }
5c19dc3a
A
270
271 return ok;
d8f41ccd
A
272}
273
5c19dc3a
A
274
275static 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;
d8f41ccd
A
288}
289
5c19dc3a 290
d8f41ccd
A
291static void askAboutAll(bool passwordFailure);
292
5c19dc3a 293
d8f41ccd
A
294static void applicantChoice(CFUserNotificationRef userNotification, CFOptionFlags responseFlags)
295{
5c19dc3a
A
296 ApplicantUIState choice;
297
298 if (kCFUserNotificationAlternateResponse == responseFlags) {
299 choice = ApplicantRejected;
300 } else if (kCFUserNotificationDefaultResponse == responseFlags) {
301 choice = ApplicantAccepted;
302 } else {
fa7225c8 303 secnotice("cjr", "Unexpected response %lu", responseFlags);
5c19dc3a
A
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) {
fa7225c8 320 secnotice("cjr", "Didn't need password to process %@", onScreen);
5c19dc3a
A
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.
fa7225c8
A
326 secnotice("cjr", "Couldn't process reject without password (e=%@) for %@ (will try with password next)", error, onScreen);
327
6b200bc3 328 if(CFErrorIsMalfunctioningKeybagError(error)){
fa7225c8
A
329 secnotice("cjr", "system is locked, dismiss the notification");
330 processApplicantsAfterUnlock = true;
331 return;
332 }
5c19dc3a
A
333 }
334 CFReleaseNull(error);
335 }
336
e0e0d90e 337 NSString *password = (__bridge NSString *) CFUserNotificationGetResponseValue(userNotification, kCFUserNotificationTextFieldValuesKey, 0);
5c19dc3a 338 if (!password) {
fa7225c8 339 secnotice("cjr", "No password given, retry");
5c19dc3a
A
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++) {
e0e0d90e 350 if (!SOSCCTryUserCredentials(CFSTR(""), (__bridge CFDataRef) passwordBytes, &error)) {
fa7225c8 351 secnotice("cjr", "Try user credentials failed %@", error);
5c19dc3a
A
352 if ((error == NULL) ||
353 (CFEqual(kSOSErrorDomain, CFErrorGetDomain(error)) && kSOSErrorWrongPassword == CFErrorGetCode(error))) {
fa7225c8 354 secnotice("cjr", "Calling askAboutAll again...");
5c19dc3a 355 [onScreen enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
e0e0d90e 356 Applicant *applicant = (Applicant*) obj;
5c19dc3a
A
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) {
fa7225c8 368 secnotice("cjr", "Can't processRequests: %@ for %@", error, onScreen);
5c19dc3a
A
369 }
370 CFReleaseNull(error);
371 }
372
373 if (processed && firstApplicantWaitingOrOnScreen()) {
374 cancelCurrentAlert(false);
375 askAboutAll(false);
376 } else {
377 cancelCurrentAlert(true);
378 }
d8f41ccd
A
379}
380
5c19dc3a 381
d8f41ccd
A
382static void passwordFailurePrompt()
383{
866f8763
A
384#pragma clang diagnostic push
385#pragma clang diagnostic ignored "-Wformat-nonliteral"
5c19dc3a 386 NSString *pwIncorrect = [NSString stringWithFormat:(NSString *)CFBridgingRelease(SecCopyCKString(SEC_CK_PASSWORD_INCORRECT)), appleIDAccountName()];
866f8763 387#pragma clang diagnostic pop
5c19dc3a
A
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;
e0e0d90e 398 CFUserNotificationRef note = CFUserNotificationCreate(NULL, 0.0, flags, &err, (__bridge CFDictionaryRef) noteAttributes);
5c19dc3a
A
399
400 if (note) {
401 CFUserNotificationReceiveResponse(note, 0.0, &flags);
402 CFRelease(note);
403 }
d8f41ccd
A
404}
405
5c19dc3a
A
406
407static 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
425static NSDictionary *createNote(Applicant *applicantToAskAbout)
426{
427 if(!applicantToAskAbout || !applicantToAskAbout.name || !applicantToAskAbout.deviceType)
428 return NULL;
429
866f8763
A
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];
5c19dc3a 433 NSString *body = [NSString stringWithFormat: getLocalizedApprovalBody(applicantToAskAbout.deviceType), appleIDAccountName()];
866f8763 434#pragma clang diagnostic pop
5c19dc3a
A
435
436 return @{
437 (id) kCFUserNotificationAlertHeaderKey : header,
438 (id) kCFUserNotificationAlertMessageKey : body,
866f8763
A
439 (id) kCFUserNotificationDefaultButtonTitleKey : (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVE),
440 (id) kCFUserNotificationAlternateButtonTitleKey: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_DECLINE),
5c19dc3a
A
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,
d8f41ccd
A
445 };
446}
447
5c19dc3a 448
d8f41ccd
A
449static void askAboutAll(bool passwordFailure)
450{
5c19dc3a 451 if ([[MCProfileConnection sharedConnection] effectiveBoolValueForSetting: MCFeatureAccountModificationAllowed] == MCRestrictedBoolExplicitNo) {
fa7225c8 452 secnotice("cjr", "Account modifications not allowed.");
5c19dc3a
A
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();
fa7225c8 472 secnotice("cjr", "Asking about: %@ (of: %@)", applicantToAskAbout, applicants);
5c19dc3a
A
473
474 NSDictionary *noteAttributes = createNote(applicantToAskAbout);
475 if(!noteAttributes) {
fa7225c8 476 secnotice("cjr", "NULL data for %@", applicantToAskAbout);
5c19dc3a
A
477 cancelCurrentAlert(true);
478 return;
479 }
480
481 CFOptionFlags flags = flagsForAsk(applicantToAskAbout);
482
483 if (currentAlert) {
e0e0d90e 484 SInt32 err = CFUserNotificationUpdate(currentAlert, 0, flags, (__bridge CFDictionaryRef) noteAttributes);
5c19dc3a 485 if (err) {
fa7225c8 486 secnotice("cjr", "CFUserNotificationUpdate err=%d", (int)err);
5c19dc3a
A
487 EXIT_LOGGED_FAILURE(EX_SOFTWARE);
488 }
489 } else {
490 SInt32 err = 0;
e0e0d90e 491 currentAlert = CFUserNotificationCreate(NULL, 0.0, flags, &err, (__bridge CFDictionaryRef) noteAttributes);
5c19dc3a 492 if (err) {
fa7225c8 493 secnotice("cjr", "Can't make notification for %@ err=%x", applicantToAskAbout, (int)err);
5c19dc3a
A
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;
d8f41ccd
A
502}
503
5c19dc3a 504
d8f41ccd
A
505static 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) {
fa7225c8 515 secnotice("cjr", "activity handler fired");
d8f41ccd
A
516 });
517}
518
5c19dc3a
A
519
520static void reminderChoice(CFUserNotificationRef userNotification, CFOptionFlags responseFlags) {
521 if (responseFlags == kCFUserNotificationAlternateResponse || responseFlags == kCFUserNotificationDefaultResponse) {
522 PersistentState *state = [PersistentState loadFromStorage];
d8f41ccd
A
523 NSDate *nowish = [NSDate new];
524 state.pendingApplicationReminder = [nowish dateByAddingTimeInterval: state.pendingApplicationReminderAlertInterval];
525 scheduleActivity(state.pendingApplicationReminderAlertInterval);
526 [state writeToStorage];
5c19dc3a
A
527 if (responseFlags == kCFUserNotificationAlternateResponse) {
528 // Use security code
d8f41ccd 529 BOOL ok = [[LSApplicationWorkspace defaultWorkspace] openSensitiveURL:[NSURL URLWithString:castleKeychainUrl] withOptions:nil];
fa7225c8 530 secnotice("cjr", "%s iCSC: opening %@ ok=%d", __FUNCTION__, castleKeychainUrl, ok);
d8f41ccd
A
531 }
532 }
533
534 cancelCurrentAlert(true);
535}
536
537
5c19dc3a
A
538static bool iCloudResetAvailable() {
539 SecureBackup *backupd = [SecureBackup new];
540 NSDictionary *backupdResults;
541 NSError *error = [backupd getAccountInfoWithInfo:nil results:&backupdResults];
fa7225c8 542 secnotice("cjr", "SecureBackup e=%@ r=%@", error, backupdResults);
5c19dc3a 543 return (error == nil && [backupdResults[kSecureBackupIsEnabledKey] isEqualToNumber:@YES]);
d8f41ccd
A
544}
545
5c19dc3a 546
5c19dc3a
A
547static 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;
d8f41ccd
A
564}
565
7fb2cbd2
A
566static 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}
5c19dc3a
A
574
575static void postApplicationReminderAlert(NSDate *nowish, PersistentState *state, unsigned int alertInterval)
d8f41ccd 576{
5c19dc3a
A
577 NSString *body = getLocalizedApplicationReminder();
578 bool has_iCSC = iCloudResetAvailable();
d8f41ccd 579
7fb2cbd2 580 if (isSOSInternalDevice() &&
e0e0d90e 581 state.defaultPendingApplicationReminderAlertInterval != state.pendingApplicationReminderAlertInterval) {
fa7225c8 582#ifdef DEBUG
5c19dc3a
A
583 body = [body stringByAppendingFormat: @"〖debug interval %u; wait time %@〗",
584 state.pendingApplicationReminderAlertInterval,
585 [nowish copyDescriptionOfIntervalSince:state.applicationDate]];
e3d460c9 586#endif
d8f41ccd 587 }
5c19dc3a 588
d8f41ccd 589 NSDictionary *pendingAttributes = @{
6b200bc3 590 (id) kCFUserNotificationAlertHeaderKey : CFBridgingRelease(SecCopyCKString(SEC_CK_REMINDER_TITLE_IOS)),
5c19dc3a 591 (id) kCFUserNotificationAlertMessageKey : body,
6b200bc3
A
592 (id) kCFUserNotificationDefaultButtonTitleKey : CFBridgingRelease(SecCopyCKString(SEC_CK_REMINDER_BUTTON_OK)),
593 (id) kCFUserNotificationAlternateButtonTitleKey: has_iCSC ? CFBridgingRelease(SecCopyCKString(SEC_CK_REMINDER_BUTTON_ICSC)) : @"",
5c19dc3a
A
594 (id) kCFUserNotificationAlertTopMostKey : @YES,
595 (__bridge id) SBUserNotificationDontDismissOnUnlock : @YES,
596 (__bridge id) SBUserNotificationDismissOnLock : @NO,
5c19dc3a 597 };
d8f41ccd 598 SInt32 err = 0;
5c19dc3a
A
599 currentAlert = CFUserNotificationCreate(NULL, 0.0, kCFUserNotificationPlainAlertLevel, &err, (__bridge CFDictionaryRef) pendingAttributes);
600
601 if (err) {
fa7225c8 602 secnotice("cjr", "Can't make pending notification err=%x", (int)err);
5c19dc3a
A
603 } else {
604 currentAlertIsForApplicants = false;
605 currentAlertSource = CFUserNotificationCreateRunLoopSource(NULL, currentAlert, reminderChoice, 0);
606 CFRunLoopAddSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode);
607 }
d8f41ccd
A
608}
609
5c19dc3a
A
610
611static void kickOutChoice(CFUserNotificationRef userNotification, CFOptionFlags responseFlags) {
fa7225c8
A
612 secnotice("cjr", "kOC %@ %lu", userNotification, responseFlags);
613
614 //default response: continue -> settings pref pane advanced keychain sync page
5c19dc3a
A
615 if (responseFlags == kCFUserNotificationDefaultResponse) {
616 // We need to let things unwind to main for the new state to get saved
617 doOnceInMain(^{
866f8763 618 NSURL *url = [NSURL URLWithString: _isAccountICDP ? rejoinICDPUrl : castleKeychainUrl];
822b670c 619 BOOL ok = [[LSApplicationWorkspace defaultWorkspace] openSensitiveURL:url withOptions:nil];
866f8763 620 secnotice("cjr","kickOutChoice account is iCDP: %d", _isAccountICDP);
fa7225c8 621 secnotice("cjr", "ok=%d opening %@", ok, url);
5c19dc3a
A
622 });
623 }
fa7225c8
A
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(^{
866f8763
A
628 if(_isAccountICDP){
629 CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init];
fa7225c8
A
630 NSError *localError = nil;
631 CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair];
b3971512
A
632
633 secnotice("followup", "Posting a follow up (for SOS) of type repair");
fa7225c8
A
634 [cdpd postFollowUpWithContext:context error:&localError ];
635 if(localError){
636 secnotice("cjr", "request to CoreCDP to follow up failed: %@", localError);
637 }
866f8763 638 else{
fa7225c8 639 secnotice("cjr", "CoreCDP handling follow up");
866f8763
A
640 _hasPostedFollowupAndStillInError = true;
641 }
fa7225c8
A
642 }
643 });
644
645 }
5c19dc3a 646 cancelCurrentAlert(true);
d8f41ccd
A
647}
648
5c19dc3a
A
649
650CFStringRef const CJRAggdDepartureReasonKey = CFSTR("com.apple.security.circlejoinrequested.departurereason");
651CFStringRef const CJRAggdNumCircleDevicesKey = CFSTR("com.apple.security.circlejoinrequested.numcircledevices");
652
d8f41ccd
A
653static void postKickedOutAlert(enum DepartureReason reason)
654{
5c19dc3a
A
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";
fa7225c8 676 secnotice("cjr", "DepartureReason %d", reason);
d8f41ccd 677 switch (reason) {
5c19dc3a
A
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;
5c19dc3a
A
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;
5c19dc3a 690
fa7225c8 691 case kSOSPasswordChanged:
5c19dc3a
A
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;
d8f41ccd 699 }
5c19dc3a 700
7fb2cbd2 701 if (isSOSInternalDevice()) {
5c19dc3a
A
702 static const char *departureReasonStrings[] = {
703 "kSOSDepartureReasonError",
704 "kSOSNeverLeftCircle",
705 "kSOSWithdrewMembership",
706 "kSOSMembershipRevoked",
707 "kSOSLeftUntrustedCircle",
708 "kSOSNeverAppliedToCircle",
709 "kSOSDiscoveredRetirement",
710 "kSOSLostPrivateKey",
fa7225c8 711 "kSOSPasswordChanged",
5c19dc3a
A
712 "unknown reason"
713 };
714 int idx = (kSOSDepartureReasonError <= reason && reason <= kSOSLostPrivateKey) ? reason : (kSOSLostPrivateKey + 1);
866f8763
A
715#pragma clang diagnostic push
716#pragma clang diagnostic ignored "-Wformat-nonliteral"
5c19dc3a
A
717 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL),
718 departureReasonStrings[idx]];
866f8763 719#pragma clang diagnostic pop
5c19dc3a
A
720 message = [message stringByAppendingString: reason_str];
721 }
722
d8f41ccd 723 NSDictionary *kickedAttributes = @{
5c19dc3a
A
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,
5c19dc3a 731 };
d8f41ccd 732 SInt32 err = 0;
fa7225c8 733
d8f41ccd 734 if (currentAlertIsForKickOut) {
5c19dc3a 735 debugState = @"pKOA B";
fa7225c8 736 secnotice("cjr", "Updating existing alert %@ with %@", currentAlert, kickedAttributes);
5c19dc3a 737 CFUserNotificationUpdate(currentAlert, 0, kCFUserNotificationPlainAlertLevel, (__bridge CFDictionaryRef) kickedAttributes);
d8f41ccd 738 } else {
5c19dc3a 739 debugState = @"pKOA C";
d8f41ccd 740
5c19dc3a
A
741 CFUserNotificationRef note = CFUserNotificationCreate(NULL, 0.0, kCFUserNotificationPlainAlertLevel, &err, (__bridge CFDictionaryRef) kickedAttributes);
742 assert((note == NULL) == (err != 0));
743 if (err) {
fa7225c8
A
744 secnotice("cjr", "Can't make kicked out notification err=%x", (int)err);
745 CFReleaseNull(note);
5c19dc3a 746 } else {
d8f41ccd
A
747 currentAlertIsForApplicants = false;
748 currentAlertIsForKickOut = true;
749
750 currentAlert = note;
fa7225c8 751 secnotice("cjr", "New ko alert %@ a=%@", currentAlert, kickedAttributes);
d8f41ccd
A
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) {
fa7225c8 757 secnotice("cjr", "Backup state might have changed (dS=%@)", debugState);
d8f41ccd
A
758 postKickedOutAlert(reason);
759 } else {
fa7225c8 760 secnotice("cjr", "Backup state may have changed, but we don't care anymore (dS=%@)", debugState);
d8f41ccd
A
761 }
762 });
fa7225c8 763
5c19dc3a 764 debugState = @"pKOA D";
d8f41ccd 765 CFRunLoopRun();
5c19dc3a 766 debugState = @"pKOA E";
d8f41ccd
A
767 notify_cancel(backupStateChangeToken);
768 }
769 }
770 debugState = @"pKOA Z";
771}
772
7fb2cbd2
A
773static void askForCDPFollowup() {
774 doOnceInMain(^{
775 NSError *localError = nil;
776 CDPFollowUpController *cdpd = [[CDPFollowUpController alloc] init];
777 CDPFollowUpContext *context = [CDPFollowUpContext contextForStateRepair];
b3971512
A
778
779 secnotice("followup", "Posting a follow up (for SOS) of type repair");
7fb2cbd2
A
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
d8f41ccd
A
791static bool processEvents()
792{
5c19dc3a
A
793 debugState = @"processEvents A";
794
b54c578e
A
795 CFErrorRef error = NULL;
796 CFErrorRef departError = NULL;
797 SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircleNonCached(&error);
8a50f688
A
798 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&departError);
799
b54c578e
A
800 BOOL abortFromError = isErrorFromXPC(error);
801 if(abortFromError && circleStatus == kSOSCCError) {
802 secnotice("cjr", "returning from processEvents due to error returned from securityd: %@", error);
8a50f688 803 return true;
b54c578e
A
804 }
805 if (departureReason == kSOSDepartureReasonError && departError && (CFEqual(sSecXPCErrorDomain, CFErrorGetDomain(departError)))) {
8a50f688
A
806 secnotice("cjr", "XPC error while checking last departure reason: \"%@\", not processing events", departError);
807 return true;
808 }
b54c578e
A
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);
8a50f688 813
b54c578e 814 // Pending application reminder
5c19dc3a 815 NSTimeInterval timeUntilApplicationAlert = [state.pendingApplicationReminder timeIntervalSinceDate:nowish];
fa7225c8 816 secnotice("cjr", "Time until pendingApplicationReminder (%@) %f", [state.pendingApplicationReminder debugDescription], timeUntilApplicationAlert);
5c19dc3a
A
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 }
866f8763
A
825
826 PSKeychainSyncIsUsingICDP();
b54c578e
A
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
866f8763 837 if(_isAccountICDP){
7fb2cbd2
A
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:
b54c578e 859 /*
7fb2cbd2
A
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.
b54c578e
A
866 */
867 if(departureReason != kSOSDepartureReasonError) {
868 secnotice("cjr", "iCDP: We need to get back into the circle");
7fb2cbd2 869 askForCDPFollowup();
b54c578e
A
870 } else {
871 secnotice("cjr", "iCDP: We appear to not be associated with an iCloud account");
872 }
ecaf5866 873 _executeProcessEventsOnce = true;
866f8763 874 return false;
7fb2cbd2
A
875 case kSOSCCRequestPending:
876 break;
877 default:
878 secnotice("cjr", "Unknown circle status %d", circleStatus);
866f8763
A
879 return false;
880 }
b54c578e 881 } else if(circleStatus == kSOSCCError && state.lastCircleStatus != kSOSCCError && (departureReason == kSOSNeverLeftCircle)) {
866f8763 882 secnotice("cjr", "SA: error from SOSCCThisDeviceIsInCircle: %@", error);
fa7225c8
A
883 CFIndex errorCode = CFErrorGetCode(error);
884 if(errorCode == kSOSErrorPublicKeyAbsent){
866f8763 885 secnotice("cjr", "SA: We need the password to re-validate ourselves - it's changed on another device");
fa7225c8
A
886 postKickedOutAlert(kSOSPasswordChanged);
887 state.lastCircleStatus = kSOSCCError;
888 [state writeToStorage];
889 return true;
890 }
891 }
5c19dc3a
A
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";
fa7225c8
A
898
899 if (state.debugShowLeftReason) {
900 secnotice("cjr", "debugShowLeftReason: %@", state.debugShowLeftReason);
5c19dc3a
A
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);
fa7225c8 909 secnotice("cjr", "Depature reason %d", departureReason);
866f8763
A
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];
b3971512
A
918
919 secnotice("followup", "Posting a follow up (for SOS) of type repair");
866f8763
A
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 }
fa7225c8 932 secnotice("cjr", "pKOA returned (cS %d lCS %d)", circleStatus, state.lastCircleStatus);
5c19dc3a 933 } else {
fa7225c8 934 secnotice("cjr", "Couldn't get last departure reason: %@", departError);
5c19dc3a 935 }
fa7225c8
A
936
937 }
5c19dc3a
A
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) {
fa7225c8 946 secnotice("cjr", "Pending request started");
5c19dc3a
A
947 state.applicationDate = nowish;
948 state.pendingApplicationReminder = [nowish dateByAddingTimeInterval: state.pendingApplicationReminderAlertInterval];
949 scheduleActivity(state.pendingApplicationReminderAlertInterval);
950 }
951 if (lastCircleStatus == kSOSCCRequestPending && circleStatus != kSOSCCRequestPending) {
fa7225c8 952 secnotice("cjr", "Pending request completed");
5c19dc3a
A
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) {
fa7225c8 968 secnotice("cjr", "-- CC after original alert gone (currentAlertIsForApplicants %d, pA %p, cA %p -- %@)",
b54c578e
A
969 currentAlertIsForApplicants, postedAlert, currentAlert, currentAlert);
970 notify_cancel(token);
971 } else {
fa7225c8 972 CFErrorRef localError = NULL;
b54c578e
A
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);
5c19dc3a
A
985 cancelCurrentAlert(true);
986 } else {
fa7225c8 987 secnotice("cjr", "Still pending...");
5c19dc3a
A
988 }
989 CFReleaseNull(localError);
990 }
991 });
992 debugState = @"processEvents D2";
fa7225c8 993 secnotice("cjr", "NOTE: currentAlertIsForApplicants %d, token %d", currentAlertIsForApplicants, notifyToken);
5c19dc3a
A
994 CFRunLoopRun();
995 return true;
996 }
fa7225c8
A
997 debugState = @"processEvents D4";
998 secnotice("cjr", "SOSCCThisDeviceIsInCircle status %d, not checking applicants", circleStatus);
5c19dc3a
A
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) {
fa7225c8 1017 secnotice("cjr", "Notified: %s", kSOSCCCircleChangedNotification);
5c19dc3a
A
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:
fa7225c8 1039 secnotice("cjr", "Update to %@ >> %@ with pending order, should work out ok though", existingApplicant, newApplicant);
5c19dc3a
A
1040 break;
1041 }
1042 } else {
1043 needsUpdate = true;
1044 applicants[newApplicant.idString] = newApplicant;
1045 }
1046 }
1047 if (copyPeerError) {
fa7225c8 1048 secnotice("cjr", "Could not update peer info array: %@", copyPeerError);
5c19dc3a
A
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) {
fa7225c8 1063 secnotice("cjr", "All applicants were handled elsewhere");
5c19dc3a 1064 cancelCurrentAlert(true);
b54c578e
A
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);
5c19dc3a
A
1075 cancelCurrentAlert(true);
1076 }
1077 if (needsUpdate) {
1078 askAboutAll(false);
1079 } else {
fa7225c8 1080 secnotice("cjr", "needsUpdate false, not updating alert");
5c19dc3a
A
1081 }
1082 // Log circleStatusError?
1083 CFReleaseNull(circleStatusError);
1084 });
fa7225c8 1085 secnotice("cjr", "ACC token %d, status %d", notify_token, notify_register_status);
5c19dc3a
A
1086 debugState = @"processEvents F2";
1087
1088 if (applicants.count == 0) {
fa7225c8 1089 secnotice("cjr", "No applicants");
5c19dc3a
A
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;
d8f41ccd
A
1105}
1106
5c19dc3a
A
1107
1108int main (int argc, const char * argv[]) {
b54c578e
A
1109 os_transaction_t txion = os_transaction_create("com.apple.security.circle-join-requested");
1110
5c19dc3a 1111 @autoreleasepool {
5c19dc3a 1112
fa7225c8
A
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
5c19dc3a 1132 int falseInARow = 0;
ecaf5866 1133 while (falseInARow < 2 && !_executeProcessEventsOnce) {
5c19dc3a 1134 if (processEvents()) {
fa7225c8 1135 secnotice("cjr", "Processed events!!!");
5c19dc3a
A
1136 falseInARow = 0;
1137 } else {
1138 falseInARow++;
1139 }
1140 cancelCurrentAlert(false);
1141 if (doOnceInMainBlockChain) {
1142 doOnceInMainBlockChain();
1143 doOnceInMainBlockChain = NULL;
1144 }
1145 }
d8f41ccd 1146 }
fa7225c8
A
1147
1148 secnotice("cjr", "Done");
b54c578e
A
1149 (void) txion; // But we really do want this around, compiler...
1150 txion = nil;
5c19dc3a 1151 return(0);
d8f41ccd 1152}