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