]> git.saurik.com Git - apple/security.git/blob - CircleJoinRequested/CircleJoinRequested.m
4c8bddd3b0920ad490996864cddd74d387f43cdf
[apple/security.git] / CircleJoinRequested / CircleJoinRequested.m
1 //
2 // CircleJoinRequested.m
3 // CircleJoinRequested
4 //
5 // Created by J Osborne on 3/5/13.
6 // Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
7 //
8 #import <Accounts/Accounts.h>
9 #import <Accounts/ACAccountStore_Private.h>
10 #import <Accounts/ACAccountType_Private.h>
11 #import <AggregateDictionary/ADClient.h>
12 #import <AppSupport/AppSupportUtils.h>
13 #import <AppleAccount/AppleAccount.h>
14 #import <AppleAccount/ACAccountStore+AppleAccount.h>
15 #import <CloudServices/SecureBackup.h>
16 #import <CoreFoundation/CFUserNotification.h>
17 #import <Foundation/Foundation.h>
18 #import <ManagedConfiguration/MCProfileConnection.h>
19 #import <ManagedConfiguration/MCFeatures.h>
20 #import <MobileCoreServices/MobileCoreServices.h>
21 #import <MobileCoreServices/LSApplicationWorkspace.h>
22 #import <MobileGestalt.h>
23 #import <ProtectedCloudStorage/CloudIdentity.h>
24 #import <Security/SecFrameworkStrings.h>
25 #import <SpringBoardServices/SBSCFUserNotificationKeys.h>
26 #include <dispatch/dispatch.h>
27 #include "SecureObjectSync/SOSCloudCircle.h"
28 #include "SecureObjectSync/SOSPeerInfo.h"
29 #include <notify.h>
30 #include <sysexits.h>
31 #import "Applicant.h"
32 #import "NSArray+map.h"
33 #import "PersistentState.h"
34 #include <xpc/private.h>
35 #include <sys/time.h>
36 #import "NSDate+TimeIntervalDescription.h"
37 #include <xpc/activity.h>
38 #include <xpc/private.h>
39 #import <syslog.h>
40 #include "utilities/SecCFRelease.h"
41 #include "utilities/debugging.h"
42
43 // As long as we are logging the failure use exit code of zero to make launchd happy
44 #define EXIT_LOGGED_FAILURE(code) xpc_transaction_end(); exit(0)
45
46 const char *kLaunchLaterXPCName = "com.apple.security.CircleJoinRequestedTick";
47 CFRunLoopSourceRef currentAlertSource = NULL;
48 CFUserNotificationRef currentAlert = NULL;
49 bool currentAlertIsForApplicants = true;
50 bool currentAlertIsForKickOut = false;
51 NSMutableDictionary *applicants = nil;
52 volatile NSString *debugState = @"main?";
53 dispatch_block_t doOnceInMainBlockChain = NULL;
54
55 NSString *castleKeychainUrl = @"prefs:root=CASTLE&path=Keychain/ADVANCED";
56 NSString *rejoinICDPUrl = @"prefs:root=CASTLE&aaaction=CDP&command=rejoin";
57
58 static void doOnceInMain(dispatch_block_t block)
59 {
60 if (doOnceInMainBlockChain) {
61 doOnceInMainBlockChain = ^{
62 doOnceInMainBlockChain();
63 block();
64 };
65 } else {
66 doOnceInMainBlockChain = block;
67 }
68 }
69
70
71 static NSString *appleIDAccountName()
72 {
73 ACAccountStore *accountStore = [[ACAccountStore alloc] init];
74 ACAccount *primaryAppleAccount = [accountStore aa_primaryAppleAccount];
75 return primaryAppleAccount.username;
76 }
77
78
79 static CFOptionFlags flagsForAsk(Applicant *applicant)
80 {
81 return kCFUserNotificationPlainAlertLevel | CFUserNotificationSecureTextField(0);
82 }
83
84
85 // NOTE: gives precedence to OnScreen
86 static Applicant *firstApplicantWaitingOrOnScreen()
87 {
88 Applicant *waiting = nil;
89 for (Applicant *applicant in [applicants objectEnumerator]) {
90 if (applicant.applicantUIState == ApplicantOnScreen) {
91 return applicant;
92 } else if (applicant.applicantUIState == ApplicantWaiting) {
93 waiting = applicant;
94 }
95 }
96
97 return waiting;
98 }
99
100
101 static NSMutableArray *applicantsInState(ApplicantUIState state)
102 {
103 NSMutableArray *results = [NSMutableArray new];
104 for (Applicant *applicant in [applicants objectEnumerator]) {
105 if (applicant.applicantUIState == state) {
106 [results addObject:applicant];
107 }
108 }
109
110 return results;
111 }
112
113
114 static BOOL processRequests(CFErrorRef *error) {
115 NSMutableArray *toAccept = [[applicantsInState(ApplicantAccepted) mapWithBlock:^id(id obj) {return (id)[obj rawPeerInfo];}] mutableCopy];
116 NSMutableArray *toReject = [[applicantsInState(ApplicantRejected) mapWithBlock:^id(id obj) {return (id)[obj rawPeerInfo];}] mutableCopy];
117 bool ok = true;
118
119 if ([toAccept count]) {
120 NSLog(@"Process accept: %@", toAccept);
121 ok = ok && SOSCCAcceptApplicants((__bridge CFArrayRef) toAccept, error);
122 if (ok) {
123 NSLog(@"kSOSCCHoldLockForInitialSync");
124 notify_post(kSOSCCHoldLockForInitialSync);
125 }
126 }
127
128 if ([toReject count]) {
129 NSLog(@"Process reject: %@", toReject);
130 ok = ok && SOSCCRejectApplicants((__bridge CFArrayRef) toReject, error);
131 }
132
133 return ok;
134 }
135
136
137 static void cancelCurrentAlert(bool stopRunLoop) {
138 if (currentAlertSource) {
139 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode);
140 CFReleaseNull(currentAlertSource);
141 }
142 if (currentAlert) {
143 CFUserNotificationCancel(currentAlert);
144 CFReleaseNull(currentAlert);
145 }
146 if (stopRunLoop) {
147 CFRunLoopStop(CFRunLoopGetCurrent());
148 }
149 currentAlertIsForKickOut = currentAlertIsForApplicants = false;
150 }
151
152
153 static void askAboutAll(bool passwordFailure);
154
155
156 static void applicantChoice(CFUserNotificationRef userNotification, CFOptionFlags responseFlags)
157 {
158 ApplicantUIState choice;
159
160 if (kCFUserNotificationAlternateResponse == responseFlags) {
161 choice = ApplicantRejected;
162 } else if (kCFUserNotificationDefaultResponse == responseFlags) {
163 choice = ApplicantAccepted;
164 } else {
165 NSLog(@"Unexpected response %lu", responseFlags);
166 choice = ApplicantRejected;
167 }
168
169 BOOL processed = NO;
170 CFErrorRef error = NULL;
171 NSArray *onScreen = applicantsInState(ApplicantOnScreen);
172
173 [onScreen enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
174 Applicant* applicant = (Applicant *) obj;
175 applicant.applicantUIState = choice;
176 }];
177
178 if (choice == ApplicantRejected) {
179 // If this device has ever set up the public key this should work without the password...
180 processed = processRequests(&error);
181 if (processed) {
182 NSLog(@"Didn't need password to process %@", onScreen);
183 cancelCurrentAlert(true);
184 return;
185 } else {
186 // ...however if the public key gets lost we should "just" fall through to the validate
187 // password path.
188 NSLog(@"Couldn't process reject without password (e=%@) for %@ (will try with password next)", error, onScreen);
189 }
190 CFReleaseNull(error);
191 }
192
193 NSString *password = (__bridge NSString *) CFUserNotificationGetResponseValue(userNotification, kCFUserNotificationTextFieldValuesKey, 0);
194 if (!password) {
195 NSLog(@"No password given, retry");
196 askAboutAll(true);
197 return;
198 }
199 const char *passwordUTF8 = [password UTF8String];
200 NSData *passwordBytes = [NSData dataWithBytes:passwordUTF8 length:strlen(passwordUTF8)];
201
202 // Sometimes securityd crashes between SOSCCRegisterUserCredentials and processRequests
203 // (which results in a process error -- I think this is 13355140), as a workaround we retry
204 // failure a few times before we give up.
205 for (int try = 0; try < 5 && !processed; try++) {
206 if (!SOSCCTryUserCredentials(CFSTR(""), (__bridge CFDataRef) passwordBytes, &error)) {
207 NSLog(@"Try user credentials failed %@", error);
208 if ((error == NULL) ||
209 (CFEqual(kSOSErrorDomain, CFErrorGetDomain(error)) && kSOSErrorWrongPassword == CFErrorGetCode(error))) {
210 NSLog(@"Calling askAboutAll again...");
211 [onScreen enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
212 Applicant *applicant = (Applicant*) obj;
213 applicant.applicantUIState = ApplicantWaiting;
214 }];
215 askAboutAll(true);
216 CFReleaseNull(error);
217 return;
218 }
219 EXIT_LOGGED_FAILURE(EX_DATAERR);
220 }
221
222 processed = processRequests(&error);
223 if (!processed) {
224 NSLog(@"Can't processRequests: %@ for %@", error, onScreen);
225 }
226 CFReleaseNull(error);
227 }
228
229 if (processed && firstApplicantWaitingOrOnScreen()) {
230 cancelCurrentAlert(false);
231 askAboutAll(false);
232 } else {
233 cancelCurrentAlert(true);
234 }
235 }
236
237
238 static void passwordFailurePrompt()
239 {
240 NSString *pwIncorrect = [NSString stringWithFormat:(NSString *)CFBridgingRelease(SecCopyCKString(SEC_CK_PASSWORD_INCORRECT)), appleIDAccountName()];
241 NSString *tryAgain = CFBridgingRelease(SecCopyCKString(SEC_CK_TRY_AGAIN));
242 NSDictionary *noteAttributes = @{
243 (id) kCFUserNotificationAlertHeaderKey : pwIncorrect,
244 (id) kCFUserNotificationDefaultButtonTitleKey : tryAgain,
245 (id) kCFUserNotificationAlertTopMostKey : @YES, // get us onto the lock screen
246 (__bridge id) SBUserNotificationDontDismissOnUnlock: @YES,
247 (__bridge id) SBUserNotificationDismissOnLock : @NO,
248 };
249 CFOptionFlags flags = kCFUserNotificationPlainAlertLevel;
250 SInt32 err;
251 CFUserNotificationRef note = CFUserNotificationCreate(NULL, 0.0, flags, &err, (__bridge CFDictionaryRef) noteAttributes);
252
253 if (note) {
254 CFUserNotificationReceiveResponse(note, 0.0, &flags);
255 CFRelease(note);
256 }
257 }
258
259
260 static NSString *getLocalizedApprovalBody(NSString *deviceType) {
261 CFStringRef applicationReminder = NULL;
262
263 if ([deviceType isEqualToString:@"iPhone"])
264 applicationReminder = SecCopyCKString(SEC_CK_APPROVAL_BODY_IOS_IPHONE);
265 else if ([deviceType isEqualToString:@"iPod"])
266 applicationReminder = SecCopyCKString(SEC_CK_APPROVAL_BODY_IOS_IPOD);
267 else if ([deviceType isEqualToString:@"iPad"])
268 applicationReminder = SecCopyCKString(SEC_CK_APPROVAL_BODY_IOS_IPAD);
269 else if ([deviceType isEqualToString:@"Mac"])
270 applicationReminder = SecCopyCKString(SEC_CK_APPROVAL_BODY_IOS_MAC);
271 else
272 applicationReminder = SecCopyCKString(SEC_CK_APPROVAL_BODY_IOS_GENERIC);
273
274 return (__bridge_transfer NSString *) applicationReminder;
275 }
276
277
278 static NSDictionary *createNote(Applicant *applicantToAskAbout)
279 {
280 if(!applicantToAskAbout || !applicantToAskAbout.name || !applicantToAskAbout.deviceType)
281 return NULL;
282
283 NSString *header = [NSString stringWithFormat: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVAL_TITLE_IOS), applicantToAskAbout.name];
284 NSString *body = [NSString stringWithFormat: getLocalizedApprovalBody(applicantToAskAbout.deviceType), appleIDAccountName()];
285
286 return @{
287 (id) kCFUserNotificationAlertHeaderKey : header,
288 (id) kCFUserNotificationAlertMessageKey : body,
289 (id) kCFUserNotificationDefaultButtonTitleKey : (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_ALLOW),
290 (id) kCFUserNotificationAlternateButtonTitleKey: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_DONT_ALLOW),
291 (id) kCFUserNotificationTextFieldTitlesKey : (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_ICLOUD_PASSWORD),
292 (id) kCFUserNotificationAlertTopMostKey : @YES, // get us onto the lock screen
293 (__bridge_transfer id) SBUserNotificationDontDismissOnUnlock: @YES,
294 (__bridge_transfer id) SBUserNotificationDismissOnLock : @NO,
295 };
296 }
297
298
299 static void askAboutAll(bool passwordFailure)
300 {
301 if ([[MCProfileConnection sharedConnection] effectiveBoolValueForSetting: MCFeatureAccountModificationAllowed] == MCRestrictedBoolExplicitNo) {
302 NSLog(@"Account modifications not allowed.");
303 return;
304 }
305
306 if (passwordFailure) {
307 passwordFailurePrompt();
308 }
309
310 if ((passwordFailure || !currentAlertIsForApplicants) && currentAlert) {
311 if (!currentAlertIsForApplicants) {
312 CFUserNotificationCancel(currentAlert);
313 }
314 // after password failure we need to remove the existing alert and supporting objects
315 // because we can't reuse them.
316 CFReleaseNull(currentAlert);
317 CFReleaseNull(currentAlertSource);
318 }
319 currentAlertIsForApplicants = true;
320
321 Applicant *applicantToAskAbout = firstApplicantWaitingOrOnScreen();
322 NSLog(@"Asking about: %@ (of: %@)", applicantToAskAbout, applicants);
323
324 NSDictionary *noteAttributes = createNote(applicantToAskAbout);
325 if(!noteAttributes) {
326 NSLog(@"NULL data for %@", applicantToAskAbout);
327 cancelCurrentAlert(true);
328 return;
329 }
330
331 CFOptionFlags flags = flagsForAsk(applicantToAskAbout);
332
333 if (currentAlert) {
334 SInt32 err = CFUserNotificationUpdate(currentAlert, 0, flags, (__bridge CFDictionaryRef) noteAttributes);
335 if (err) {
336 NSLog(@"CFUserNotificationUpdate err=%d", (int)err);
337 EXIT_LOGGED_FAILURE(EX_SOFTWARE);
338 }
339 } else {
340 SInt32 err = 0;
341 currentAlert = CFUserNotificationCreate(NULL, 0.0, flags, &err, (__bridge CFDictionaryRef) noteAttributes);
342 if (err) {
343 NSLog(@"Can't make notification for %@ err=%x", applicantToAskAbout, (int)err);
344 EXIT_LOGGED_FAILURE(EX_SOFTWARE);
345 }
346
347 currentAlertSource = CFUserNotificationCreateRunLoopSource(NULL, currentAlert, applicantChoice, 0);
348 CFRunLoopAddSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode);
349 }
350
351 applicantToAskAbout.applicantUIState = ApplicantOnScreen;
352 }
353
354
355 static void scheduleActivity(int alertInterval)
356 {
357 xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0);
358 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, alertInterval);
359 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, XPC_ACTIVITY_INTERVAL_1_MIN);
360 xpc_dictionary_set_bool(options, XPC_ACTIVITY_REPEATING, false);
361 xpc_dictionary_set_bool(options, XPC_ACTIVITY_ALLOW_BATTERY, true);
362 xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY);
363
364 xpc_activity_register(kLaunchLaterXPCName, options, ^(xpc_activity_t activity) {
365 NSLog(@"activity handler fired");
366 });
367 }
368
369
370 static void reminderChoice(CFUserNotificationRef userNotification, CFOptionFlags responseFlags) {
371 if (responseFlags == kCFUserNotificationAlternateResponse || responseFlags == kCFUserNotificationDefaultResponse) {
372 PersistentState *state = [PersistentState loadFromStorage];
373 NSDate *nowish = [NSDate new];
374 state.pendingApplicationReminder = [nowish dateByAddingTimeInterval: state.pendingApplicationReminderAlertInterval];
375 scheduleActivity(state.pendingApplicationReminderAlertInterval);
376 [state writeToStorage];
377 if (responseFlags == kCFUserNotificationAlternateResponse) {
378 // Use security code
379 BOOL ok = [[LSApplicationWorkspace defaultWorkspace] openSensitiveURL:[NSURL URLWithString:castleKeychainUrl] withOptions:nil];
380 NSLog(@"%s iCSC: opening %@ ok=%d", __FUNCTION__, castleKeychainUrl, ok);
381 }
382 }
383
384 cancelCurrentAlert(true);
385 }
386
387
388 static bool iCloudResetAvailable() {
389 SecureBackup *backupd = [SecureBackup new];
390 NSDictionary *backupdResults;
391 NSError *error = [backupd getAccountInfoWithInfo:nil results:&backupdResults];
392 NSLog(@"SecureBackup e=%@ r=%@", error, backupdResults);
393 return (error == nil && [backupdResults[kSecureBackupIsEnabledKey] isEqualToNumber:@YES]);
394 }
395
396
397
398 static NSString *getLocalizedApplicationReminder() {
399 CFStringRef applicationReminder = NULL;
400 switch (MGGetSInt32Answer(kMGQDeviceClassNumber, MGDeviceClassInvalid)) {
401 case MGDeviceClassiPhone:
402 applicationReminder = SecCopyCKString(SEC_CK_REMINDER_BODY_IOS_IPHONE);
403 break;
404 case MGDeviceClassiPod:
405 applicationReminder = SecCopyCKString(SEC_CK_REMINDER_BODY_IOS_IPOD);
406 break;
407 case MGDeviceClassiPad:
408 applicationReminder = SecCopyCKString(SEC_CK_REMINDER_BODY_IOS_IPAD);
409 break;
410 default:
411 applicationReminder = SecCopyCKString(SEC_CK_REMINDER_BODY_IOS_GENERIC);
412 break;
413 }
414 return (__bridge_transfer NSString *) applicationReminder;
415 }
416
417
418 static void postApplicationReminderAlert(NSDate *nowish, PersistentState *state, unsigned int alertInterval)
419 {
420 NSString *body = getLocalizedApplicationReminder();
421 bool has_iCSC = iCloudResetAvailable();
422
423 if (CPIsInternalDevice() &&
424 state.defaultPendingApplicationReminderAlertInterval != state.pendingApplicationReminderAlertInterval) {
425 #if !defined(NDEBUG)
426 body = [body stringByAppendingFormat: @"〖debug interval %u; wait time %@〗",
427 state.pendingApplicationReminderAlertInterval,
428 [nowish copyDescriptionOfIntervalSince:state.applicationDate]];
429 #endif
430 }
431
432 NSDictionary *pendingAttributes = @{
433 (id) kCFUserNotificationAlertHeaderKey : (__bridge NSString *) SecCopyCKString(SEC_CK_REMINDER_TITLE_IOS),
434 (id) kCFUserNotificationAlertMessageKey : body,
435 (id) kCFUserNotificationDefaultButtonTitleKey : (__bridge NSString *) SecCopyCKString(SEC_CK_REMINDER_BUTTON_OK),
436 (id) kCFUserNotificationAlternateButtonTitleKey: has_iCSC ? (__bridge NSString *) SecCopyCKString(SEC_CK_REMINDER_BUTTON_ICSC) : @"",
437 (id) kCFUserNotificationAlertTopMostKey : @YES,
438 (__bridge id) SBUserNotificationDontDismissOnUnlock : @YES,
439 (__bridge id) SBUserNotificationDismissOnLock : @NO,
440 (__bridge id) SBUserNotificationOneButtonPerLine : @YES,
441 };
442 SInt32 err = 0;
443 currentAlert = CFUserNotificationCreate(NULL, 0.0, kCFUserNotificationPlainAlertLevel, &err, (__bridge CFDictionaryRef) pendingAttributes);
444
445 if (err) {
446 NSLog(@"Can't make pending notification err=%x", (int)err);
447 } else {
448 currentAlertIsForApplicants = false;
449 currentAlertSource = CFUserNotificationCreateRunLoopSource(NULL, currentAlert, reminderChoice, 0);
450 CFRunLoopAddSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode);
451 }
452 }
453
454
455 static void kickOutChoice(CFUserNotificationRef userNotification, CFOptionFlags responseFlags) {
456 NSLog(@"kOC %@ %lu", userNotification, responseFlags);
457 if (responseFlags == kCFUserNotificationDefaultResponse) {
458 // We need to let things unwind to main for the new state to get saved
459 doOnceInMain(^{
460 ACAccountStore *store = [ACAccountStore new];
461 ACAccount *primary = [store aa_primaryAppleAccount];
462 NSString *dsid = [primary aa_personID];
463 bool localICDP = false;
464 if (dsid) {
465 NSDictionary *options = @{ (__bridge id) kPCSSetupDSID : dsid, };
466 PCSIdentitySetRef identity = PCSIdentitySetCreate((__bridge CFDictionaryRef) options, NULL, NULL);
467
468 if (identity) {
469 localICDP = PCSIdentitySetIsICDP(identity, NULL);
470 CFRelease(identity);
471 }
472 }
473 NSURL *url = [NSURL URLWithString: localICDP ? rejoinICDPUrl : castleKeychainUrl];
474 BOOL ok = [[LSApplicationWorkspace defaultWorkspace] openSensitiveURL:url withOptions:nil];
475 NSLog(@"ok=%d opening %@", ok, url);
476 });
477 }
478 cancelCurrentAlert(true);
479 }
480
481
482 CFStringRef const CJRAggdDepartureReasonKey = CFSTR("com.apple.security.circlejoinrequested.departurereason");
483 CFStringRef const CJRAggdNumCircleDevicesKey = CFSTR("com.apple.security.circlejoinrequested.numcircledevices");
484
485 static void postKickedOutAlert(enum DepartureReason reason)
486 {
487 NSString *header = nil;
488 NSString *message = nil;
489
490 // <rdar://problem/20358568> iCDP telemetry: ☂ Statistics on circle reset and drop out events
491 ADClientSetValueForScalarKey(CJRAggdDepartureReasonKey, reason);
492
493 int64_t num_peers = 0;
494 CFArrayRef peerList = SOSCCCopyPeerPeerInfo(NULL);
495 if (peerList) {
496 num_peers = CFArrayGetCount(peerList);
497 if (num_peers > 99) {
498 // Round down # peers to 2 significant digits
499 int factor;
500 for (factor = 10; num_peers >= 100*factor; factor *= 10) ;
501 num_peers = (num_peers / factor) * factor;
502 }
503 CFRelease(peerList);
504 }
505 ADClientSetValueForScalarKey(CJRAggdNumCircleDevicesKey, num_peers);
506
507 debugState = @"pKOA A";
508 syslog(LOG_ERR, "DepartureReason %d", reason);
509 switch (reason) {
510 case kSOSDiscoveredRetirement:
511 case kSOSLostPrivateKey:
512 case kSOSWithdrewMembership:
513 // Was: SEC_CK_CR_BODY_WITHDREW
514 // "... if you turn off a switch you have some idea why the light is off" - Murf
515 return;
516 break;
517
518 case kSOSNeverAppliedToCircle:
519 // We didn't get kicked out, we were never here. This should only happen if we changed iCloud accounts
520 // (and we had sync on in the previous one, and never had it on in the new one). As this is explicit
521 // user action alot of the "Light switch" argument (above) applies.
522 return;
523 break;
524
525 case kSOSNeverLeftCircle:
526 case kSOSMembershipRevoked:
527 case kSOSLeftUntrustedCircle:
528 default:
529 header = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
530 message = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_IOS);
531 break;
532 }
533
534 if (CPIsInternalDevice()) {
535 static const char *departureReasonStrings[] = {
536 "kSOSDepartureReasonError",
537 "kSOSNeverLeftCircle",
538 "kSOSWithdrewMembership",
539 "kSOSMembershipRevoked",
540 "kSOSLeftUntrustedCircle",
541 "kSOSNeverAppliedToCircle",
542 "kSOSDiscoveredRetirement",
543 "kSOSLostPrivateKey",
544 "unknown reason"
545 };
546 int idx = (kSOSDepartureReasonError <= reason && reason <= kSOSLostPrivateKey) ? reason : (kSOSLostPrivateKey + 1);
547 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL),
548 departureReasonStrings[idx]];
549 message = [message stringByAppendingString: reason_str];
550 }
551
552 NSDictionary *kickedAttributes = @{
553 (id) kCFUserNotificationAlertHeaderKey : header,
554 (id) kCFUserNotificationAlertMessageKey : message,
555 (id) kCFUserNotificationDefaultButtonTitleKey : (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE),
556 (id) kCFUserNotificationAlternateButtonTitleKey: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW),
557 (id) kCFUserNotificationAlertTopMostKey : @YES,
558 (__bridge id) SBUserNotificationDismissOnLock : @NO,
559 (__bridge id) SBUserNotificationDontDismissOnUnlock : @YES,
560 (__bridge id) SBUserNotificationOneButtonPerLine : @YES,
561 };
562 SInt32 err = 0;
563
564 if (currentAlertIsForKickOut) {
565 debugState = @"pKOA B";
566 NSLog(@"Updating existing alert %@ with %@", currentAlert, kickedAttributes);
567 CFUserNotificationUpdate(currentAlert, 0, kCFUserNotificationPlainAlertLevel, (__bridge CFDictionaryRef) kickedAttributes);
568 } else {
569 debugState = @"pKOA C";
570
571 CFUserNotificationRef note = CFUserNotificationCreate(NULL, 0.0, kCFUserNotificationPlainAlertLevel, &err, (__bridge CFDictionaryRef) kickedAttributes);
572 assert((note == NULL) == (err != 0));
573 if (err) {
574 NSLog(@"Can't make kicked out notification err=%x", (int)err);
575 } else {
576 currentAlertIsForApplicants = false;
577 currentAlertIsForKickOut = true;
578
579 currentAlert = note;
580 NSLog(@"New ko alert %@ a=%@", currentAlert, kickedAttributes);
581 currentAlertSource = CFUserNotificationCreateRunLoopSource(NULL, currentAlert, kickOutChoice, 0);
582 CFRunLoopAddSource(CFRunLoopGetCurrent(), currentAlertSource, kCFRunLoopDefaultMode);
583 int backupStateChangeToken;
584 notify_register_dispatch("com.apple.EscrowSecurityAlert.reset", &backupStateChangeToken, dispatch_get_main_queue(), ^(int token) {
585 if (currentAlert == note) {
586 NSLog(@"Backup state might have changed (dS=%@)", debugState);
587 postKickedOutAlert(reason);
588 } else {
589 NSLog(@"Backup state may have changed, but we don't care anymore (dS=%@)", debugState);
590 }
591 });
592 debugState = @"pKOA D";
593 CFRunLoopRun();
594 debugState = @"pKOA E";
595 notify_cancel(backupStateChangeToken);
596 }
597 }
598 debugState = @"pKOA Z";
599 }
600
601
602 static bool processEvents()
603 {
604 debugState = @"processEvents A";
605
606 CFErrorRef error = NULL;
607 CFErrorRef departError = NULL;
608 SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircle(&error);
609 NSDate *nowish = [NSDate date];
610 PersistentState *state = [PersistentState loadFromStorage];
611 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&departError);
612 NSLog(@"CircleStatus %d -> %d{%d} (s=%p)", state.lastCircleStatus, circleStatus, departureReason, state);
613
614
615 // Pending application reminder
616 NSTimeInterval timeUntilApplicationAlert = [state.pendingApplicationReminder timeIntervalSinceDate:nowish];
617 NSLog(@"Time until pendingApplicationReminder (%@) %f", [state.pendingApplicationReminder debugDescription], timeUntilApplicationAlert);
618 if (circleStatus == kSOSCCRequestPending) {
619 if (timeUntilApplicationAlert <= 0) {
620 debugState = @"reminderAlert";
621 postApplicationReminderAlert(nowish, state, state.pendingApplicationReminderAlertInterval);
622 } else {
623 scheduleActivity(ceil(timeUntilApplicationAlert));
624 }
625 }
626
627
628 // No longer in circle?
629 if ((state.lastCircleStatus == kSOSCCInCircle && (circleStatus == kSOSCCNotInCircle || circleStatus == kSOSCCCircleAbsent)) ||
630 (state.lastCircleStatus == kSOSCCCircleAbsent && circleStatus == kSOSCCNotInCircle && state.absentCircleWithNoReason) ||
631 state.debugShowLeftReason) {
632 // Used to be in the circle, now we aren't - tell the user why
633 debugState = @"processEvents B";
634
635 if (state.debugShowLeftReason) {
636 NSLog(@"debugShowLeftReason: %@", state.debugShowLeftReason);
637 departureReason = [state.debugShowLeftReason intValue];
638 state.debugShowLeftReason = nil;
639 CFReleaseNull(departError);
640 [state writeToStorage];
641 }
642
643 if (departureReason != kSOSDepartureReasonError) {
644 state.absentCircleWithNoReason = (circleStatus == kSOSCCCircleAbsent && departureReason == kSOSNeverLeftCircle);
645 NSLog(@"Depature reason %d", departureReason);
646 postKickedOutAlert(departureReason);
647 NSLog(@"pKOA returned (cS %d lCS %d)", circleStatus, state.lastCircleStatus);
648 } else {
649 NSLog(@"Couldn't get last departure reason: %@", departError);
650 }
651 }
652
653
654 // Circle applications: pending request(s) started / completed
655 debugState = @"processEvents C";
656 if (circleStatus != state.lastCircleStatus) {
657 SOSCCStatus lastCircleStatus = state.lastCircleStatus;
658 state.lastCircleStatus = circleStatus;
659
660 if (lastCircleStatus != kSOSCCRequestPending && circleStatus == kSOSCCRequestPending) {
661 NSLog(@"Pending request started");
662 state.applicationDate = nowish;
663 state.pendingApplicationReminder = [nowish dateByAddingTimeInterval: state.pendingApplicationReminderAlertInterval];
664 scheduleActivity(state.pendingApplicationReminderAlertInterval);
665 }
666 if (lastCircleStatus == kSOSCCRequestPending && circleStatus != kSOSCCRequestPending) {
667 NSLog(@"Pending request completed");
668 state.applicationDate = [NSDate distantPast];
669 state.pendingApplicationReminder = [NSDate distantFuture];
670 }
671
672 [state writeToStorage];
673 }
674
675 if (circleStatus != kSOSCCInCircle) {
676 if (circleStatus == kSOSCCRequestPending && currentAlert) {
677 int notifyToken = 0;
678 CFUserNotificationRef postedAlert = currentAlert;
679
680 debugState = @"processEvents D1";
681 notify_register_dispatch(kSOSCCCircleChangedNotification, &notifyToken, dispatch_get_main_queue(), ^(int token) {
682 if (postedAlert != currentAlert) {
683 NSLog(@"-- CC after original alert gone (currentAlertIsForApplicants %d, pA %p, cA %p -- %@)",
684 currentAlertIsForApplicants, postedAlert, currentAlert, currentAlert);
685 notify_cancel(token);
686 } else {
687 CFErrorRef localError = NULL;
688 SOSCCStatus newCircleStatus = SOSCCThisDeviceIsInCircle(&localError);
689 if (newCircleStatus != kSOSCCRequestPending) {
690 if (newCircleStatus == kSOSCCError)
691 NSLog(@"No longer pending (nCS=%d, alert=%@) error: %@", newCircleStatus, currentAlert, localError);
692 else
693 NSLog(@"No longer pending (nCS=%d, alert=%@)", newCircleStatus, currentAlert);
694 cancelCurrentAlert(true);
695 } else {
696 NSLog(@"Still pending...");
697 }
698 CFReleaseNull(localError);
699 }
700 });
701 debugState = @"processEvents D2";
702 NSLog(@"NOTE: currentAlertIsForApplicants %d, token %d", currentAlertIsForApplicants, notifyToken);
703 CFRunLoopRun();
704 return true;
705 }
706 debugState = @"processEvents D4";
707 NSLog(@"SOSCCThisDeviceIsInCircle status %d, not checking applicants", circleStatus);
708 return false;
709 }
710
711
712 // Applicants
713 debugState = @"processEvents E";
714 applicants = [NSMutableDictionary new];
715 for (id applicantInfo in (__bridge_transfer NSArray *) SOSCCCopyApplicantPeerInfo(&error)) {
716 Applicant *applicant = [[Applicant alloc] initWithPeerInfo:(__bridge SOSPeerInfoRef) applicantInfo];
717 applicants[applicant.idString] = applicant;
718 }
719
720 // Log error from SOSCCCopyApplicantPeerInfo() above?
721 CFReleaseNull(error);
722
723 int notify_token = -42;
724 debugState = @"processEvents F";
725 int notify_register_status = notify_register_dispatch(kSOSCCCircleChangedNotification, &notify_token, dispatch_get_main_queue(), ^(int token) {
726 NSLog(@"Notified: %s", kSOSCCCircleChangedNotification);
727 CFErrorRef circleStatusError = NULL;
728
729 bool needsUpdate = false;
730 CFErrorRef copyPeerError = NULL;
731 NSMutableSet *newIds = [NSMutableSet new];
732 for (id applicantInfo in (__bridge_transfer NSArray *) SOSCCCopyApplicantPeerInfo(&copyPeerError)) {
733 Applicant *newApplicant = [[Applicant alloc] initWithPeerInfo:(__bridge SOSPeerInfoRef) applicantInfo];
734 [newIds addObject:newApplicant.idString];
735 Applicant *existingApplicant = applicants[newApplicant.idString];
736 if (existingApplicant) {
737 switch (existingApplicant.applicantUIState) {
738 case ApplicantWaiting:
739 applicants[newApplicant.idString] = newApplicant;
740 break;
741
742 case ApplicantOnScreen:
743 newApplicant.applicantUIState = ApplicantOnScreen;
744 applicants[newApplicant.idString] = newApplicant;
745 break;
746
747 default:
748 NSLog(@"Update to %@ >> %@ with pending order, should work out ok though", existingApplicant, newApplicant);
749 break;
750 }
751 } else {
752 needsUpdate = true;
753 applicants[newApplicant.idString] = newApplicant;
754 }
755 }
756 if (copyPeerError) {
757 NSLog(@"Could not update peer info array: %@", copyPeerError);
758 CFRelease(copyPeerError);
759 return;
760 }
761
762 NSMutableArray *idsToRemoveFromApplicants = [NSMutableArray new];
763 for (NSString *exisitngId in [applicants keyEnumerator]) {
764 if (![newIds containsObject:exisitngId]) {
765 [idsToRemoveFromApplicants addObject:exisitngId];
766 needsUpdate = true;
767 }
768 }
769 [applicants removeObjectsForKeys:idsToRemoveFromApplicants];
770
771 if (newIds.count == 0) {
772 NSLog(@"All applicants were handled elsewhere");
773 cancelCurrentAlert(true);
774 }
775 SOSCCStatus currentCircleStatus = SOSCCThisDeviceIsInCircle(&circleStatusError);
776 if (kSOSCCInCircle != currentCircleStatus) {
777 NSLog(@"Left circle (%d), not handling remaining %lu applicants", currentCircleStatus, (unsigned long)newIds.count);
778 cancelCurrentAlert(true);
779 }
780 if (needsUpdate) {
781 askAboutAll(false);
782 } else {
783 NSLog(@"needsUpdate false, not updating alert");
784 }
785 // Log circleStatusError?
786 CFReleaseNull(circleStatusError);
787 });
788 NSLog(@"ACC token %d, status %d", notify_token, notify_register_status);
789 debugState = @"processEvents F2";
790
791 if (applicants.count == 0) {
792 NSLog(@"No applicants");
793 } else {
794 debugState = @"processEvents F3";
795 askAboutAll(false);
796 debugState = @"processEvents F4";
797 if (currentAlert) {
798 debugState = @"processEvents F5";
799 CFRunLoopRun();
800 }
801 }
802
803 debugState = @"processEvents F6";
804 notify_cancel(notify_token);
805 debugState = @"processEvents DONE";
806
807 return false;
808 }
809
810
811 int main (int argc, const char * argv[]) {
812 xpc_transaction_begin();
813
814 @autoreleasepool {
815 // NOTE: DISPATCH_QUEUE_PRIORITY_LOW will not actually manage to drain events in a lot of cases (like circleStatus != kSOSCCInCircle)
816 xpc_set_event_stream_handler("com.apple.notifyd.matching", dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(xpc_object_t object) {
817 char *event_description = xpc_copy_description(object);
818 NSLog(@"notifyd event: %s\nAlert (%p) %s %s\ndebugState: %@", event_description, currentAlert,
819 currentAlertIsForApplicants ? "for applicants" : "!applicants",
820 currentAlertIsForKickOut ? "KO" : "!KO", debugState);
821 free(event_description);
822 });
823
824 xpc_activity_register(kLaunchLaterXPCName, XPC_ACTIVITY_CHECK_IN, ^(xpc_activity_t activity) {
825 });
826
827 int falseInARow = 0;
828 while (falseInARow < 2) {
829 if (processEvents()) {
830 falseInARow = 0;
831 } else {
832 falseInARow++;
833 }
834 cancelCurrentAlert(false);
835 if (doOnceInMainBlockChain) {
836 doOnceInMainBlockChain();
837 doOnceInMainBlockChain = NULL;
838 }
839 }
840 }
841
842 NSLog(@"Done");
843 xpc_transaction_end();
844 return(0);
845 }