]> git.saurik.com Git - apple/security.git/blob - OSX/sec/SOSCircle/Tool/keychain_sync.m
Security-58286.1.32.tar.gz
[apple/security.git] / OSX / sec / SOSCircle / Tool / keychain_sync.m
1 /*
2 * Copyright (c) 2003-2007,2009-2010,2013-2014 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 * keychain_add.c
24 */
25
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <sys/utsname.h>
32 #include <sys/stat.h>
33 #include <time.h>
34
35 #include <Security/SecItem.h>
36
37 #include <CoreFoundation/CFNumber.h>
38 #include <CoreFoundation/CFString.h>
39
40 #include <Security/SecureObjectSync/SOSCloudCircle.h>
41 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h>
42 #include <Security/SecureObjectSync/SOSPeerInfo.h>
43 #include <Security/SecureObjectSync/SOSPeerInfoPriv.h>
44 #include <Security/SecureObjectSync/SOSPeerInfoV2.h>
45 #include <Security/SecureObjectSync/SOSUserKeygen.h>
46 #include <Security/SecureObjectSync/SOSKVSKeys.h>
47 #include <securityd/SOSCloudCircleServer.h>
48 #include <Security/SecureObjectSync/SOSBackupSliceKeyBag.h>
49 #include <Security/SecOTRSession.h>
50 #include <SOSCircle/CKBridge/SOSCloudKeychainClient.h>
51
52 #include <utilities/SecCFWrappers.h>
53 #include <utilities/debugging.h>
54
55 #include <SecurityTool/readline.h>
56 #include <notify.h>
57
58 #include "keychain_sync.h"
59 #include "keychain_log.h"
60 #include "syncbackup.h"
61
62 #include "secToolFileIO.h"
63 #include "secViewDisplay.h"
64
65 #include <Security/SecPasswordGenerate.h>
66
67 #define MAXKVSKEYTYPE kUnknownKey
68 #define DATE_LENGTH 18
69
70
71 static bool clearAllKVS(CFErrorRef *error)
72 {
73 __block bool result = false;
74 const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
75 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
76 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
77 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
78
79 SOSCloudKeychainClearAll(processQueue, ^(CFDictionaryRef returnedValues, CFErrorRef cerror)
80 {
81 result = (cerror != NULL);
82 dispatch_semaphore_signal(waitSemaphore);
83 });
84
85 dispatch_semaphore_wait(waitSemaphore, finishTime);
86
87 return result;
88 }
89
90 static const char *getSOSCCStatusDescription(SOSCCStatus ccstatus)
91 {
92 switch (ccstatus)
93 {
94 case kSOSCCInCircle: return "In Circle";
95 case kSOSCCNotInCircle: return "Not in Circle";
96 case kSOSCCRequestPending: return "Request pending";
97 case kSOSCCCircleAbsent: return "Circle absent";
98 case kSOSCCError: return "Circle error";
99
100 default:
101 return "<unknown ccstatus>";
102 break;
103 }
104 }
105
106 static void printPeerInfos(char *label, CFArrayRef (^getArray)(CFErrorRef *error)) {
107 CFErrorRef error = NULL;
108 CFArrayRef ppi = getArray(&error);
109 SOSPeerInfoRef me = SOSCCCopyMyPeerInfo(NULL);
110 CFStringRef mypeerID = SOSPeerInfoGetPeerID(me);
111
112 if(ppi) {
113 printmsg(CFSTR("%s count: %ld\n"), label, (long)CFArrayGetCount(ppi));
114 CFArrayForEach(ppi, ^(const void *value) {
115 char buf[160];
116 SOSPeerInfoRef peer = (SOSPeerInfoRef)value;
117 CFIndex version = SOSPeerInfoGetVersion(peer);
118 CFStringRef peerName = SOSPeerInfoGetPeerName(peer);
119 CFStringRef devtype = SOSPeerInfoGetPeerDeviceType(peer);
120 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
121 CFStringRef transportType = CFSTR("KVS");
122 CFStringRef deviceID = CFSTR("");
123 CFDictionaryRef gestalt = SOSPeerInfoCopyPeerGestalt(peer);
124 CFStringRef osVersion = CFDictionaryGetValue(gestalt, CFSTR("OSVersion"));
125 CFReleaseNull(gestalt);
126
127
128 if(version >= 2){
129 CFDictionaryRef v2Dictionary = peer->v2Dictionary;
130 transportType = CFDictionaryGetValue(v2Dictionary, sTransportType);
131 deviceID = CFDictionaryGetValue(v2Dictionary, sDeviceID);
132 }
133 char *pname = CFStringToCString(peerName);
134 char *dname = CFStringToCString(devtype);
135 char *tname = CFStringToCString(transportType);
136 char *iname = CFStringToCString(deviceID);
137 char *osname = CFStringToCString(osVersion);
138 const char *me = CFEqualSafe(mypeerID, peerID) ? "me>" : " ";
139
140
141 snprintf(buf, 160, "%s %s: %-16s %-16s %-16s %-16s", me, label, pname, dname, tname, iname);
142
143 free(pname);
144 free(dname);
145 CFStringRef pid = SOSPeerInfoGetPeerID(peer);
146 CFIndex vers = SOSPeerInfoGetVersion(peer);
147 printmsg(CFSTR("%s %@ V%d OS:%s\n"), buf, pid, vers, osname);
148 free(osname);
149 });
150 } else {
151 printmsg(CFSTR("No %s, error: %@\n"), label, error);
152 }
153 CFReleaseNull(ppi);
154 CFReleaseNull(error);
155 }
156
157 static void dumpCircleInfo()
158 {
159 CFErrorRef error = NULL;
160 CFArrayRef generations = NULL;
161 CFArrayRef confirmedDigests = NULL;
162 bool is_accountKeyIsTrusted = false;
163 __block int count = 0;
164
165 SOSCCStatus ccstatus = SOSCCThisDeviceIsInCircle(&error);
166 if(ccstatus == kSOSCCError) {
167 printmsg(CFSTR("End of Dump - unable to proceed due to ccstatus (%s) error: %@\n"), getSOSCCStatusDescription(ccstatus), error);
168 return;
169 }
170 printmsg(CFSTR("ccstatus: %s (%d)\n"), getSOSCCStatusDescription(ccstatus), ccstatus, error);
171
172 is_accountKeyIsTrusted = SOSCCValidateUserPublic(&error);
173 if(is_accountKeyIsTrusted)
174 printmsg(CFSTR("Account user public is trusted%@"),CFSTR("\n"));
175 else
176 printmsg(CFSTR("Account user public is not trusted error:(%@)\n"), error);
177 CFReleaseNull(error);
178
179 generations = SOSCCCopyGenerationPeerInfo(&error);
180 if(generations) {
181 CFArrayForEach(generations, ^(const void *value) {
182 count++;
183 if(count%2 == 0)
184 printmsg(CFSTR("Circle name: %@, "),value);
185
186 if(count%2 != 0) {
187 CFStringRef genDesc = SOSGenerationCountCopyDescription(value);
188 printmsg(CFSTR("Generation Count: %@"), genDesc);
189 CFReleaseNull(genDesc);
190 }
191 printmsg(CFSTR("%s\n"), "");
192 });
193 } else {
194 printmsg(CFSTR("No generation count: %@\n"), error);
195 }
196 CFReleaseNull(generations);
197 CFReleaseNull(error);
198
199 printPeerInfos(" Peers", ^(CFErrorRef *error) { return SOSCCCopyValidPeerPeerInfo(error); });
200 printPeerInfos(" Invalid", ^(CFErrorRef *error) { return SOSCCCopyNotValidPeerPeerInfo(error); });
201 printPeerInfos(" Retired", ^(CFErrorRef *error) { return SOSCCCopyRetirementPeerInfo(error); });
202 printPeerInfos(" Concur", ^(CFErrorRef *error) { return SOSCCCopyConcurringPeerPeerInfo(error); });
203 printPeerInfos("Applicants", ^(CFErrorRef *error) { return SOSCCCopyApplicantPeerInfo(error); });
204
205 if (!SOSCCForEachEngineStateAsString(&error, ^(CFStringRef oneStateString) {
206 printmsg(CFSTR("%@\n"), oneStateString);
207 })) {
208 printmsg(CFSTR("No engine peers: %@\n"), error);
209 }
210
211 CFReleaseNull(error);
212 CFReleaseNull(confirmedDigests);
213 }
214
215 static bool enableDefaultViews()
216 {
217 bool result = false;
218 CFMutableSetRef viewsToEnable = SOSViewCopyViewSet(kViewSetV0);
219 CFMutableSetRef viewsToDisable = CFSetCreateMutable(NULL, 0, NULL);
220
221 result = SOSCCViewSet(viewsToEnable, viewsToDisable);
222 CFRelease(viewsToEnable);
223 CFRelease(viewsToDisable);
224 return result;
225 }
226
227 static bool requestToJoinCircle(CFErrorRef *error)
228 {
229 // Set the visual state of switch based on membership in circle
230 bool hadError = false;
231 SOSCCStatus ccstatus = SOSCCThisDeviceIsInCircle(error);
232
233 switch (ccstatus)
234 {
235 case kSOSCCCircleAbsent:
236 hadError = !SOSCCResetToOffering(error);
237 hadError &= enableDefaultViews();
238 break;
239 case kSOSCCNotInCircle:
240 hadError = !SOSCCRequestToJoinCircle(error);
241 hadError &= enableDefaultViews();
242 break;
243 default:
244 printerr(CFSTR("Request to join circle with bad status: %@ (%d)\n"), SOSCCGetStatusDescription(ccstatus), ccstatus);
245 break;
246 }
247 return hadError;
248 }
249
250 static bool setPassword(char *labelAndPassword, CFErrorRef *err)
251 {
252 char *last = NULL;
253 char *token0 = strtok_r(labelAndPassword, ":", &last);
254 char *token1 = strtok_r(NULL, "", &last);
255 CFStringRef label = token1 ? CFStringCreateWithCString(NULL, token0, kCFStringEncodingUTF8) : CFSTR("security command line tool");
256 char *password_token = token1 ? token1 : token0;
257 password_token = password_token ? password_token : "";
258 CFDataRef password = CFDataCreate(NULL, (const UInt8*) password_token, strlen(password_token));
259 bool returned = !SOSCCSetUserCredentials(label, password, err);
260 CFRelease(label);
261 CFRelease(password);
262 return returned;
263 }
264
265 static bool tryPassword(char *labelAndPassword, CFErrorRef *err)
266 {
267 char *last = NULL;
268 char *token0 = strtok_r(labelAndPassword, ":", &last);
269 char *token1 = strtok_r(NULL, "", &last);
270 CFStringRef label = token1 ? CFStringCreateWithCString(NULL, token0, kCFStringEncodingUTF8) : CFSTR("security command line tool");
271 char *password_token = token1 ? token1 : token0;
272 password_token = password_token ? password_token : "";
273 CFDataRef password = CFDataCreate(NULL, (const UInt8*) password_token, strlen(password_token));
274 bool returned = !SOSCCTryUserCredentials(label, password, err);
275 CFRelease(label);
276 CFRelease(password);
277 return returned;
278 }
279
280 static CFTypeRef getObjectsFromCloud(CFArrayRef keysToGet, dispatch_queue_t processQueue, dispatch_group_t dgroup)
281 {
282 __block CFTypeRef object = NULL;
283
284 const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
285 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
286 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
287
288 dispatch_group_enter(dgroup);
289
290 CloudKeychainReplyBlock replyBlock =
291 ^ (CFDictionaryRef returnedValues, CFErrorRef error)
292 {
293 secinfo("sync", "SOSCloudKeychainGetObjectsFromCloud returned: %@", returnedValues);
294 object = returnedValues;
295 if (object)
296 CFRetain(object);
297 if (error)
298 {
299 secerror("SOSCloudKeychainGetObjectsFromCloud returned error: %@", error);
300 }
301 dispatch_group_leave(dgroup);
302 secinfo("sync", "SOSCloudKeychainGetObjectsFromCloud block exit: %@", object);
303 dispatch_semaphore_signal(waitSemaphore);
304 };
305
306 if (!keysToGet)
307 SOSCloudKeychainGetAllObjectsFromCloud(processQueue, replyBlock);
308 else
309 SOSCloudKeychainGetObjectsFromCloud(keysToGet, processQueue, replyBlock);
310
311 dispatch_semaphore_wait(waitSemaphore, finishTime);
312 if (object && (CFGetTypeID(object) == CFNullGetTypeID())) // return a NULL instead of a CFNull
313 {
314 CFRelease(object);
315 object = NULL;
316 }
317 secerror("returned: %@", object);
318 return object;
319 }
320
321 static CFStringRef printFullDataString(CFDataRef data){
322 __block CFStringRef fullData = NULL;
323
324 BufferPerformWithHexString(CFDataGetBytePtr(data), CFDataGetLength(data), ^(CFStringRef dataHex) {
325 fullData = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@"), dataHex);
326 });
327
328 return fullData;
329 }
330
331 static void displayLastKeyParameters(CFTypeRef key, CFTypeRef value)
332 {
333 CFDataRef valueAsData = asData(value, NULL);
334 if(valueAsData){
335 CFDataRef dateData = CFDataCreateCopyFromRange(kCFAllocatorDefault, valueAsData, CFRangeMake(0, DATE_LENGTH));
336 CFDataRef keyParameterData = CFDataCreateCopyFromPositions(kCFAllocatorDefault, valueAsData, DATE_LENGTH, CFDataGetLength(valueAsData));
337 CFStringRef dateString = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, dateData, kCFStringEncodingUTF8);
338 CFStringRef keyParameterDescription = UserParametersDescription(keyParameterData);
339 if(keyParameterDescription)
340 printmsg(CFSTR("%@: %@: %@\n"), key, dateString, keyParameterDescription);
341 else
342 printmsg(CFSTR("%@: %@\n"), key, printFullDataString(value));
343 CFReleaseNull(dateString);
344 CFReleaseNull(keyParameterData);
345 CFReleaseNull(dateData);
346 CFReleaseNull(keyParameterDescription);
347 }
348 else{
349 printmsg(CFSTR("%@: %@\n"), key, value);
350 }
351 }
352
353 static void displayKeyParameters(CFTypeRef key, CFTypeRef value)
354 {
355 if(isData(value)){
356 CFStringRef keyParameterDescription = UserParametersDescription((CFDataRef)value);
357
358 if(keyParameterDescription)
359 printmsg(CFSTR("%@: %@\n"), key, keyParameterDescription);
360 else
361 printmsg(CFSTR("%@: %@\n"), key, value);
362
363 CFReleaseNull(keyParameterDescription);
364 }
365 else{
366 printmsg(CFSTR("%@: %@\n"), key, value);
367 }
368 }
369
370 static void displayLastCircle(CFTypeRef key, CFTypeRef value)
371 {
372 CFDataRef valueAsData = asData(value, NULL);
373 if(valueAsData){
374 CFErrorRef localError = NULL;
375
376 CFDataRef dateData = CFDataCreateCopyFromRange(kCFAllocatorDefault, valueAsData, CFRangeMake(0, DATE_LENGTH));
377 CFDataRef circleData = CFDataCreateCopyFromPositions(kCFAllocatorDefault, valueAsData, DATE_LENGTH, CFDataGetLength(valueAsData));
378 CFStringRef dateString = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, dateData, kCFStringEncodingUTF8);
379 SOSCircleRef circle = SOSCircleCreateFromData(NULL, (CFDataRef) circleData, &localError);
380
381 if(circle){
382 CFIndex size = 5;
383 CFNumberRef idLength = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &size);
384 CFDictionaryRef format = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, CFSTR("SyncD"), CFSTR("SyncD"), CFSTR("idLength"), idLength, NULL);
385 printmsgWithFormatOptions(format, CFSTR("%@: %@: %@\n"), key, dateString, circle);
386 CFReleaseNull(idLength);
387 CFReleaseNull(format);
388
389 }
390 else
391 printmsg(CFSTR("%@: %@\n"), key, printFullDataString(circleData));
392
393 CFReleaseNull(dateString);
394 CFReleaseNull(circleData);
395 CFReleaseSafe(circle);
396 CFReleaseNull(dateData);
397 CFReleaseNull(localError);
398 }
399 else{
400 printmsg(CFSTR("%@: %@\n"), key, value);
401 }
402 }
403
404 static void displayCircle(CFTypeRef key, CFTypeRef value)
405 {
406 CFDataRef circleData = (CFDataRef)value;
407
408 CFErrorRef localError = NULL;
409 if (isData(circleData))
410 {
411 CFIndex size = 5;
412 CFNumberRef idLength = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &size);
413 CFDictionaryRef format = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, CFSTR("SyncD"), CFSTR("SyncD"), CFSTR("idLength"), idLength, NULL);
414 SOSCircleRef circle = SOSCircleCreateFromData(NULL, circleData, &localError);
415 printmsgWithFormatOptions(format, CFSTR("%@: %@\n"), key, circle);
416 CFReleaseSafe(circle);
417 CFReleaseNull(idLength);
418 CFReleaseNull(format);
419
420 }
421 else
422 printmsg(CFSTR("%@: %@\n"), key, value);
423 }
424
425 static void displayMessage(CFTypeRef key, CFTypeRef value)
426 {
427 CFDataRef message = (CFDataRef)value;
428 if(isData(message)){
429 const char* messageType = SecOTRPacketTypeString(message);
430 printmsg(CFSTR("%@: %s: %ld\n"), key, messageType, CFDataGetLength(message));
431 }
432 else
433 printmsg(CFSTR("%@: %@\n"), key, value);
434 }
435
436 static void printEverything(CFTypeRef objects)
437 {
438 CFDictionaryForEach(objects, ^(const void *key, const void *value) {
439 if (isData(value))
440 {
441 printmsg(CFSTR("%@: %@\n\n"), key, printFullDataString(value));
442 }
443 else
444 printmsg(CFSTR("%@: %@\n"), key, value);
445 });
446
447 }
448
449 static void decodeForKeyType(CFTypeRef key, CFTypeRef value, SOSKVSKeyType type){
450 switch (type) {
451 case kCircleKey:
452 displayCircle(key, value);
453 break;
454 case kRetirementKey:
455 case kMessageKey:
456 displayMessage(key, value);
457 break;
458 case kParametersKey:
459 displayKeyParameters(key, value);
460 break;
461 case kLastKeyParameterKey:
462 displayLastKeyParameters(key, value);
463 break;
464 case kLastCircleKey:
465 displayLastCircle(key, value);
466 break;
467 case kInitialSyncKey:
468 case kAccountChangedKey:
469 case kDebugInfoKey:
470 case kRingKey:
471 default:
472 printmsg(CFSTR("%@: %@\n"), key, value);
473 break;
474 }
475 }
476
477 static void decodeAllTheValues(CFTypeRef objects){
478 SOSKVSKeyType keyType = 0;
479 __block bool didPrint = false;
480
481 for (keyType = 0; keyType <= MAXKVSKEYTYPE; keyType++){
482 CFDictionaryForEach(objects, ^(const void *key, const void *value) {
483 if(SOSKVSKeyGetKeyType(key) == keyType){
484 decodeForKeyType(key, value, keyType);
485 didPrint = true;
486 }
487 });
488 if(didPrint)
489 printmsg(CFSTR("%@\n"), CFSTR(""));
490 didPrint = false;
491 }
492 }
493 static bool dumpKVS(char *itemName, CFErrorRef *err)
494 {
495 CFArrayRef keysToGet = NULL;
496 if (itemName)
497 {
498 CFStringRef itemStr = CFStringCreateWithCString(kCFAllocatorDefault, itemName, kCFStringEncodingUTF8);
499 fprintf(outFile, "Retrieving %s from KVS\n", itemName);
500 keysToGet = CFArrayCreateForCFTypes(kCFAllocatorDefault, itemStr, NULL);
501 CFReleaseSafe(itemStr);
502 }
503 dispatch_queue_t generalq = dispatch_queue_create("general", DISPATCH_QUEUE_SERIAL);
504 dispatch_group_t work_group = dispatch_group_create();
505 CFTypeRef objects = getObjectsFromCloud(keysToGet, generalq, work_group);
506 CFReleaseSafe(keysToGet);
507 if (objects)
508 {
509 fprintf(outFile, "All keys and values straight from KVS\n");
510 printEverything(objects);
511 fprintf(outFile, "\nAll values in decoded form...\n");
512 decodeAllTheValues(objects);
513 }
514 fprintf(outFile, "\n");
515 return true;
516 }
517
518 static bool syncAndWait(CFErrorRef *err)
519 {
520 __block CFTypeRef objects = NULL;
521
522 dispatch_queue_t generalq = dispatch_queue_create("general", DISPATCH_QUEUE_SERIAL);
523
524 const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC;
525 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0);
526 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds);
527
528 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error)
529 {
530 secinfo("sync", "SOSCloudKeychainSynchronizeAndWait returned: %@", returnedValues);
531 if (error)
532 secerror("SOSCloudKeychainSynchronizeAndWait returned error: %@", error);
533 objects = CFRetainSafe(returnedValues);
534
535 secinfo("sync", "SOSCloudKeychainGetObjectsFromCloud block exit: %@", objects);
536 dispatch_semaphore_signal(waitSemaphore);
537 };
538
539 SOSCloudKeychainSynchronizeAndWait(generalq, replyBlock);
540
541 dispatch_semaphore_wait(waitSemaphore, finishTime);
542
543 dumpKVS(NULL, NULL);
544 fprintf(outFile, "\n");
545 return false;
546 }
547
548 static CFStringRef convertStringToProperty(char *propertyname) {
549 CFStringRef propertyspec = NULL;
550
551 if(strcmp(propertyname, "hasentropy") == 0) {
552 propertyspec = kSOSSecPropertyHasEntropy;
553 } else if(strcmp(propertyname, "screenlock") == 0) {
554 propertyspec = kSOSSecPropertyScreenLock;
555 } else if(strcmp(propertyname, "SEP") == 0) {
556 propertyspec = kSOSSecPropertySEP;
557 } else if(strcmp(propertyname, "IOS") == 0) {
558 propertyspec = kSOSSecPropertyIOS;
559 }
560 return propertyspec;
561 }
562
563
564 static CFStringRef convertPropertyReturnCodeToString(SOSSecurityPropertyResultCode ac) {
565 CFStringRef retval = NULL;
566 switch(ac) {
567 case kSOSCCGeneralSecurityPropertyError:
568 retval = CFSTR("General Error"); break;
569 case kSOSCCSecurityPropertyValid:
570 retval = CFSTR("Is Member of Security Property"); break;
571 case kSOSCCSecurityPropertyNotValid:
572 retval = CFSTR("Is Not Member of Security Property"); break;
573 case kSOSCCSecurityPropertyNotQualified:
574 retval = CFSTR("Is not qualified for Security Property"); break;
575 case kSOSCCNoSuchSecurityProperty:
576 retval = CFSTR("No Such Security Property"); break;
577 }
578 return retval;
579 }
580
581
582 static bool SecPropertycmd(char *itemName, CFErrorRef *err) {
583 char *cmd, *propertyname;
584 SOSSecurityPropertyActionCode ac = kSOSCCSecurityPropertyQuery;
585 CFStringRef propertyspec;
586
587 propertyname = strchr(itemName, ':');
588 if(propertyname == NULL) return false;
589 *propertyname = 0;
590 propertyname++;
591 cmd = itemName;
592
593 if(strcmp(cmd, "enable") == 0) {
594 ac = kSOSCCSecurityPropertyEnable;
595 } else if(strcmp(cmd, "disable") == 0) {
596 ac = kSOSCCSecurityPropertyDisable;
597 } else if(strcmp(cmd, "query") == 0) {
598 ac = kSOSCCSecurityPropertyQuery;
599 } else {
600 return false;
601 }
602
603 propertyspec = convertStringToProperty(propertyname);
604 if(!propertyspec) return false;
605
606 SOSSecurityPropertyResultCode rc = SOSCCSecurityProperty(propertyspec, ac, err);
607 CFStringRef resultString = convertPropertyReturnCodeToString(rc);
608
609 printmsg(CFSTR("Property Result: %@ : %@\n"), resultString, propertyspec);
610 return true;
611 }
612
613
614 static void dumpStringSet(CFStringRef label, CFSetRef s) {
615 if(!s || !label) return;
616
617 printmsg(CFSTR("%@: { "), label);
618 __block bool first = true;
619 CFSetForEach(s, ^(const void *p) {
620 CFStringRef fmt = CFSTR(", %@");
621 if(first) {
622 fmt = CFSTR("%@");
623 }
624 CFStringRef string = (CFStringRef) p;
625 printmsg(fmt, string);
626 first=false;
627 });
628 printmsg(CFSTR(" }\n"), NULL);
629 }
630
631 static bool dumpMyPeer(CFErrorRef *error) {
632 SOSPeerInfoRef myPeer = SOSCCCopyMyPeerInfo(error);
633
634 if (!myPeer) return false;
635
636 CFStringRef peerID = SOSPeerInfoGetPeerID(myPeer);
637 CFStringRef peerName = SOSPeerInfoGetPeerName(myPeer);
638 CFIndex peerVersion = SOSPeerInfoGetVersion(myPeer);
639 bool retirement = SOSPeerInfoIsRetirementTicket(myPeer);
640
641 printmsg(CFSTR("Peer Name: %@ PeerID: %@ Version: %d\n"), peerName, peerID, peerVersion);
642 if(retirement) {
643 CFDateRef retdate = SOSPeerInfoGetRetirementDate(myPeer);
644 printmsg(CFSTR("Retired: %@\n"), retdate);
645
646 }
647
648 if(peerVersion >= 2) {
649 CFMutableSetRef views = SOSPeerInfoV2DictionaryCopySet(myPeer, sViewsKey);
650 CFStringRef serialNumber = SOSPeerInfoV2DictionaryCopyString(myPeer, sSerialNumberKey);
651 CFBooleanRef preferIDS = SOSPeerInfoV2DictionaryCopyBoolean(myPeer, sPreferIDS);
652 CFBooleanRef preferIDSFragmentation = SOSPeerInfoV2DictionaryCopyBoolean(myPeer, sPreferIDSFragmentation);
653 CFBooleanRef preferIDSACKModel = SOSPeerInfoV2DictionaryCopyBoolean(myPeer, sPreferIDSACKModel);
654 CFStringRef transportType = SOSPeerInfoV2DictionaryCopyString(myPeer, sTransportType);
655 CFStringRef idsDeviceID = SOSPeerInfoV2DictionaryCopyString(myPeer, sDeviceID);
656 CFMutableSetRef properties = SOSPeerInfoV2DictionaryCopySet(myPeer, sSecurityPropertiesKey);
657
658 printmsg(CFSTR("Serial#: %@ PrefIDS#: %@ PrefFragmentation#: %@ PrefACK#: %@ transportType#: %@ idsDeviceID#: %@\n"),
659 serialNumber, preferIDS, preferIDSFragmentation, preferIDSACKModel, transportType, idsDeviceID);
660 dumpStringSet(CFSTR(" Views: "), views);
661 dumpStringSet(CFSTR("SecurityProperties: "), properties);
662
663 CFReleaseSafe(serialNumber);
664 CFReleaseSafe(preferIDS);
665 CFReleaseSafe(preferIDSFragmentation);
666 CFReleaseSafe(views);
667 CFReleaseSafe(transportType);
668 CFReleaseSafe(idsDeviceID);
669 CFReleaseSafe(properties);
670
671 }
672
673 return myPeer != NULL;
674 }
675
676 static bool setBag(char *itemName, CFErrorRef *err)
677 {
678 __block bool success = false;
679 __block CFErrorRef error = NULL;
680
681 CFStringRef random = SecPasswordCreateWithRandomDigits(10, NULL);
682
683 CFStringPerformWithUTF8CFData(random, ^(CFDataRef stringAsData) {
684 if (0 == strncasecmp(optarg, "single", 6) || 0 == strncasecmp(optarg, "all", 3)) {
685 bool includeV0 = (0 == strncasecmp(optarg, "all", 3));
686 printmsg(CFSTR("Setting iCSC single using entropy from string: %@\n"), random);
687 CFDataRef aks_bag = SecAKSCopyBackupBagWithSecret(CFDataGetLength(stringAsData), (uint8_t*)CFDataGetBytePtr(stringAsData), &error);
688
689 if (aks_bag) {
690 success = SOSCCRegisterSingleRecoverySecret(aks_bag, includeV0, &error);
691 if (!success) {
692 printmsg(CFSTR("Failed registering single secret %@"), error);
693 CFReleaseNull(aks_bag);
694 }
695 } else {
696 printmsg(CFSTR("Failed to create aks_bag: %@"), error);
697 }
698 CFReleaseNull(aks_bag);
699 } else if (0 == strncasecmp(optarg, "device", 6)) {
700 printmsg(CFSTR("Setting Device Secret using entropy from string: %@\n"), random);
701
702 SOSPeerInfoRef me = SOSCCCopyMyPeerWithNewDeviceRecoverySecret(stringAsData, &error);
703
704 success = me != NULL;
705
706 if (!success)
707 printmsg(CFSTR("Failed: %@\n"), err);
708 CFReleaseNull(me);
709 } else {
710 printmsg(CFSTR("Unrecognized argument to -b %s\n"), optarg);
711 }
712 });
713
714
715 return success;
716 }
717
718 static void prClientViewState(char *label, bool result) {
719 fprintf(outFile, "Sync Status for %s: %s\n", label, (result) ? "enabled": "not enabled");
720 }
721
722 static bool clientViewStatus(CFErrorRef *error) {
723 prClientViewState("KeychainV0", SOSCCIsIcloudKeychainSyncing());
724 prClientViewState("Safari", SOSCCIsSafariSyncing());
725 prClientViewState("AppleTV", SOSCCIsAppleTVSyncing());
726 prClientViewState("HomeKit", SOSCCIsHomeKitSyncing());
727 prClientViewState("Wifi", SOSCCIsWiFiSyncing());
728 prClientViewState("AlwaysOnNoInitialSync", SOSCCIsContinuityUnlockSyncing());
729 return false;
730 }
731
732
733 static bool dumpYetToSync(CFErrorRef *error) {
734 CFArrayRef yetToSyncViews = SOSCCCopyYetToSyncViewsList(error);
735
736 bool hadError = yetToSyncViews;
737
738 if (yetToSyncViews) {
739 __block CFStringRef separator = CFSTR("");
740
741 printmsg(CFSTR("Yet to sync views: ["), NULL);
742
743 CFArrayForEach(yetToSyncViews, ^(const void *value) {
744 if (isString(value)) {
745 printmsg(CFSTR("%@%@"), separator, value);
746
747 separator = CFSTR(", ");
748 }
749 });
750 printmsg(CFSTR("]\n"), NULL);
751 }
752
753 return !hadError;
754 }
755
756
757 // enable, disable, accept, reject, status, Reset, Clear
758 int
759 keychain_sync(int argc, char * const *argv)
760 {
761 /*
762 "Keychain Syncing"
763 " -d disable"
764 " -e enable (join/create circle)"
765 " -i info (current status)"
766 " -m dump my peer"
767 "
768 "Account/Circle Management"
769 " -a accept all applicants"
770 " -l [reason] sign out of circle + set custom departure reason"
771 " -q sign out of circle"
772 " -r reject all applicants"
773 " -E ensure fresh parameters"
774 " -b device|all|single Register a backup bag - THIS RESETS BACKUPS!\n"
775 " -A Apply to a ring\n"
776 " -B Withdrawl from a ring\n"
777 " -G Enable Ring\n"
778 " -F Ring Status\n"
779 " -I Dump Ring Information\n"
780
781 " -N (re-)set to new account (USE WITH CARE: device will not leave circle before resetting account!)"
782 " -O reset to offering"
783 " -R reset circle"
784 " -X [limit] best effort bail from circle in limit seconds"
785 " -o list view unaware peers in circle"
786 " -0 boot view unaware peers from circle"
787 " -1 grab account state from the keychain"
788 " -2 delete account state from the keychain"
789 " -3 grab engine state from the keychain"
790 " -4 delete engine state from the keychain"
791 " -5 cleanup old KVS keys in KVS"
792 " -6 [test]populate KVS with garbage KVS keys
793 "
794 "IDS"
795 " -g set IDS device id"
796 " -p retrieve IDS device id"
797 " -x ping all devices in an IDS account"
798 " -w check IDS availability"
799 " -z retrieve IDS id through KeychainSyncingOverIDSProxy"
800 "
801 "Password"
802 " -P [label:]password set password (optionally for a given label) for sync"
803 " -T [label:]password try password (optionally for a given label) for sync"
804 "
805 "KVS"
806 " -k pend all registered kvs keys"
807 " -C clear all values from KVS"
808 " -D [itemName] dump contents of KVS"
809 " -W sync and dump"
810 "
811 "Misc"
812 " -v [enable|disable|query:viewname] enable, disable, or query my PeerInfo's view set"
813 " viewnames are: keychain|masterkey|iclouddrive|photos|cloudkit|escrow|fde|maildrop|icloudbackup|notes|imessage|appletv|homekit|"
814 " wifi|passwords|creditcards|icloudidentity|othersyncable"
815 " -L list all known view and their status"
816 " -S [enable|disable|propertyname] enable, disable, or query my PeerInfo's Security Property set"
817 " propertynames are: hasentropy|screenlock|SEP|IOS\n"
818 " -U purge private key material cache\n"
819 " -V Report View Sync Status on all known clients.\n"
820 " -H Set escrow record.\n"
821 " -J Get the escrow record.\n"
822 " -M Check peer availability.\n"
823 */
824 int ch, result = 0;
825 CFErrorRef error = NULL;
826 bool hadError = false;
827 SOSLogSetOutputTo(NULL, NULL);
828
829 while ((ch = getopt(argc, argv, "ab:deg:hikl:mopq:rSv:w:x:zA:B:MNJCDEF:HG:ILOP:RT:UWX:VY0123456")) != -1)
830 switch (ch) {
831 case 'l':
832 {
833 fprintf(outFile, "Signing out of circle\n");
834 hadError = !SOSCCSignedOut(true, &error);
835 if (!hadError) {
836 errno = 0;
837 int reason = (int) strtoul(optarg, NULL, 10);
838 if (errno != 0 ||
839 reason < kSOSDepartureReasonError ||
840 reason >= kSOSNumDepartureReasons) {
841 fprintf(errFile, "Invalid custom departure reason %s\n", optarg);
842 } else {
843 fprintf(outFile, "Setting custom departure reason %d\n", reason);
844 hadError = !SOSCCSetLastDepartureReason(reason, &error);
845 notify_post(kSOSCCCircleChangedNotification);
846 }
847 }
848 break;
849 }
850
851 case 'q':
852 {
853 fprintf(outFile, "Signing out of circle\n");
854 bool signOutImmediately = false;
855 if (strcasecmp(optarg, "true") == 0) {
856 signOutImmediately = true;
857 } else if (strcasecmp(optarg, "false") == 0) {
858 signOutImmediately = false;
859 } else {
860 fprintf(outFile, "Please provide a \"true\" or \"false\" whether you'd like to leave the circle immediately\n");
861 }
862 hadError = !SOSCCSignedOut(signOutImmediately, &error);
863 notify_post(kSOSCCCircleChangedNotification);
864 break;
865 }
866
867 case 'p':
868 {
869 fprintf(outFile, "Grabbing DS ID\n");
870 CFStringRef deviceID = SOSCCCopyDeviceID(&error);
871 if (error) {
872 hadError = true;
873 break;
874 }
875 if (!isNull(deviceID)) {
876 const char *id = CFStringGetCStringPtr(deviceID, kCFStringEncodingUTF8);
877 if (id)
878 fprintf(outFile, "IDS Device ID: %s\n", id);
879 else
880 fprintf(outFile, "IDS Device ID is null!\n");
881 }
882 CFReleaseNull(deviceID);
883 break;
884 }
885
886 case 'g':
887 {
888 fprintf(outFile, "Setting DS ID: %s\n", optarg);
889 CFStringRef deviceID = CFStringCreateWithCString(kCFAllocatorDefault, optarg, kCFStringEncodingUTF8);
890 hadError = SOSCCSetDeviceID(deviceID, &error);
891 CFReleaseNull(deviceID);
892 break;
893 }
894
895 case 'w':
896 {
897 fprintf(outFile, "Attempting to send this message over IDS: %s\n", optarg);
898 CFStringRef message = CFStringCreateWithCString(kCFAllocatorDefault, optarg, kCFStringEncodingUTF8);
899 hadError = SOSCCIDSServiceRegistrationTest(message, &error);
900 if (error) {
901 printerr(CFSTR("IDS is not ready: %@\n"), error);
902 CFRelease(error);
903 }
904 CFReleaseNull(message);
905 break;
906 }
907
908 case 'x':
909 {
910 fprintf(outFile, "Starting ping test using this message: %s\n", optarg);
911 CFStringRef message = CFStringCreateWithCString(kCFAllocatorDefault, optarg, kCFStringEncodingUTF8);
912 hadError = SOSCCIDSPingTest(message, &error);
913 if (error) {
914 printerr(CFSTR("Ping test failed to start: %@\n"), error);
915 CFRelease(error);
916 }
917 CFReleaseNull(message);
918 break;
919 }
920
921 case 'z':
922 hadError = SOSCCIDSDeviceIDIsAvailableTest(&error);
923 if (error) {
924 printerr(CFSTR("Failed to retrieve IDS device ID: %@\n"), error);
925 CFRelease(error);
926 }
927 break;
928
929 case 'e':
930 fprintf(outFile, "Turning ON keychain syncing\n");
931 hadError = requestToJoinCircle(&error);
932 break;
933
934 case 'd':
935 fprintf(outFile, "Turning OFF keychain syncing\n");
936 hadError = !SOSCCRemoveThisDeviceFromCircle(&error);
937 break;
938
939 case 'a':
940 {
941 CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL);
942 if (applicants) {
943 hadError = !SOSCCAcceptApplicants(applicants, &error);
944 CFRelease(applicants);
945 } else {
946 fprintf(errFile, "No applicants to accept\n");
947 }
948 break;
949 }
950
951 case 'r':
952 {
953 CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL);
954 if (applicants) {
955 hadError = !SOSCCRejectApplicants(applicants, &error);
956 CFRelease(applicants);
957 } else {
958 fprintf(errFile, "No applicants to reject\n");
959 }
960 break;
961 }
962
963 case 'i':
964 dumpCircleInfo();
965 break;
966
967 case 'k':
968 notify_post("com.apple.security.cloudkeychain.forceupdate");
969 break;
970
971 case 'o':
972 {
973 printPeerInfos("view-unaware", ^(CFErrorRef *error) { return SOSCCCopyViewUnawarePeerInfo(error); });
974 break;
975 }
976
977 case '0':
978 {
979 CFArrayRef unawares = SOSCCCopyViewUnawarePeerInfo(&error);
980 if (unawares) {
981 hadError = !SOSCCRemovePeersFromCircle(unawares, &error);
982 } else {
983 hadError = true;
984 }
985 CFReleaseNull(unawares);
986 break;
987 }
988 case '1':
989 {
990 CFDataRef accountState = SOSCCCopyAccountState(&error);
991 if (accountState) {
992 printmsg(CFSTR(" %@\n"), CFDataCopyHexString(accountState));
993 } else {
994 hadError = true;
995 }
996 CFReleaseNull(accountState);
997 break;
998 }
999 case '2':
1000 {
1001 bool status = SOSCCDeleteAccountState(&error);
1002 if (status) {
1003 printmsg(CFSTR("Deleted account from the keychain %d\n"), status);
1004 } else {
1005 hadError = true;
1006 }
1007 break;
1008 }
1009 case '3':
1010 {
1011 CFDataRef engineState = SOSCCCopyEngineData(&error);
1012 if (engineState) {
1013 printmsg(CFSTR(" %@\n"), CFDataCopyHexString(engineState));
1014 } else {
1015 hadError = true;
1016 }
1017 CFReleaseNull(engineState);
1018 break;
1019 }
1020 case '4':
1021 {
1022 bool status = SOSCCDeleteEngineState(&error);
1023 if (status) {
1024 printmsg(CFSTR("Deleted engine-state from the keychain %d\n"), status);
1025 } else {
1026 hadError = true;
1027 }
1028 break;
1029 }
1030 case '5' :
1031 {
1032 bool result = SOSCCCleanupKVSKeys(&error);
1033 if(result)
1034 {
1035 printmsg(CFSTR("Got all the keys from KVS %d\n"), result);
1036 }else {
1037 hadError = true;
1038 }
1039 break;
1040 }
1041 case '6' :
1042 {
1043 bool result = SOSCCTestPopulateKVSWithBadKeys(&error);
1044 if(result)
1045 {
1046 printmsg(CFSTR("Populated KVS with garbage %d\n"), result);
1047 }else {
1048 hadError = true;
1049 }
1050 break;
1051 }
1052 case 'E':
1053 {
1054 fprintf(outFile, "Ensuring Fresh Parameters\n");
1055 bool result = SOSCCRequestEnsureFreshParameters(&error);
1056 if (error) {
1057 hadError = true;
1058 break;
1059 }
1060 if (result) {
1061 fprintf(outFile, "Refreshed Parameters Ensured!\n");
1062 } else {
1063 fprintf(outFile, "Problem trying to ensure fresh parameters\n");
1064 }
1065 break;
1066 }
1067 case 'A':
1068 {
1069 fprintf(outFile, "Applying to Ring\n");
1070 CFStringRef ringName = CFStringCreateWithCString(kCFAllocatorDefault, (char *)optarg, kCFStringEncodingUTF8);
1071 hadError = SOSCCApplyToARing(ringName, &error);
1072 CFReleaseNull(ringName);
1073 break;
1074 }
1075 case 'B':
1076 {
1077 fprintf(outFile, "Withdrawing from Ring\n");
1078 CFStringRef ringName = CFStringCreateWithCString(kCFAllocatorDefault, (char *)optarg, kCFStringEncodingUTF8);
1079 hadError = SOSCCWithdrawlFromARing(ringName, &error);
1080 CFReleaseNull(ringName);
1081 break;
1082 }
1083 case 'F':
1084 {
1085 fprintf(outFile, "Status of this device in the Ring\n");
1086 CFStringRef ringName = CFStringCreateWithCString(kCFAllocatorDefault, (char *)optarg, kCFStringEncodingUTF8);
1087 hadError = SOSCCRingStatus(ringName, &error);
1088 CFReleaseNull(ringName);
1089 break;
1090 }
1091 case 'G':
1092 {
1093 fprintf(outFile, "Enabling Ring\n");
1094 CFStringRef ringName = CFStringCreateWithCString(kCFAllocatorDefault, (char *)optarg, kCFStringEncodingUTF8);
1095 hadError = SOSCCEnableRing(ringName, &error);
1096 CFReleaseNull(ringName);
1097 break;
1098 }
1099 case 'H':
1100 {
1101 fprintf(outFile, "Setting random escrow record\n");
1102 bool success = SOSCCSetEscrowRecord(CFSTR("label"), 8, &error);
1103 if(success)
1104 hadError = false;
1105 else
1106 hadError = true;
1107 break;
1108 }
1109 case 'J':
1110 {
1111 CFDictionaryRef attempts = SOSCCCopyEscrowRecord(&error);
1112 if(attempts){
1113 CFDictionaryForEach(attempts, ^(const void *key, const void *value) {
1114 if(isString(key)){
1115 char *keyString = CFStringToCString(key);
1116 fprintf(outFile, "%s:\n", keyString);
1117 free(keyString);
1118 }
1119 if(isDictionary(value)){
1120 CFDictionaryForEach(value, ^(const void *key, const void *value) {
1121 if(isString(key)){
1122 char *keyString = CFStringToCString(key);
1123 fprintf(outFile, "%s: ", keyString);
1124 free(keyString);
1125 }
1126 if(isString(value)){
1127 char *time = CFStringToCString(value);
1128 fprintf(outFile, "timestamp: %s\n", time);
1129 free(time);
1130 }
1131 else if(isNumber(value)){
1132 uint64_t tries;
1133 CFNumberGetValue(value, kCFNumberLongLongType, &tries);
1134 fprintf(outFile, "date: %llu\n", tries);
1135 }
1136 });
1137 }
1138
1139 });
1140 }
1141 CFReleaseNull(attempts);
1142 hadError = false;
1143 break;
1144 }
1145 case 'M':
1146 {
1147 bool success = SOSCCCheckPeerAvailability(&error);
1148 if(success)
1149 hadError = false;
1150 else
1151 hadError = true;
1152 break;
1153 }
1154 case 'I':
1155 {
1156 fprintf(outFile, "Printing all the rings\n");
1157 CFStringRef ringdescription = SOSCCGetAllTheRings(&error);
1158 if(!ringdescription)
1159 hadError = true;
1160 else
1161 fprintf(outFile, "Rings: %s", CFStringToCString(ringdescription));
1162
1163 break;
1164 }
1165
1166 case 'N':
1167 hadError = !SOSCCAccountSetToNew(&error);
1168 if (!hadError)
1169 notify_post(kSOSCCCircleChangedNotification);
1170 break;
1171
1172 case 'R':
1173 hadError = !SOSCCResetToEmpty(&error);
1174 break;
1175
1176 case 'O':
1177 hadError = !SOSCCResetToOffering(&error);
1178 break;
1179
1180 case 'm':
1181 hadError = !dumpMyPeer(&error);
1182 break;
1183
1184 case 'C':
1185 hadError = clearAllKVS(&error);
1186 break;
1187
1188 case 'P':
1189 hadError = setPassword(optarg, &error);
1190 break;
1191
1192 case 'T':
1193 hadError = tryPassword(optarg, &error);
1194 break;
1195
1196 case 'X':
1197 {
1198 uint64_t limit = strtoul(optarg, NULL, 10);
1199 hadError = !SOSCCBailFromCircle_BestEffort(limit, &error);
1200 break;
1201 }
1202
1203 case 'U':
1204 hadError = !SOSCCPurgeUserCredentials(&error);
1205 break;
1206
1207 case 'D':
1208 hadError = !dumpKVS(optarg, &error);
1209 break;
1210
1211 case 'W':
1212 hadError = syncAndWait(&error);
1213 break;
1214
1215 case 'v':
1216 hadError = !viewcmd(optarg, &error);
1217 break;
1218
1219 case 'V':
1220 hadError = clientViewStatus(&error);
1221 break;
1222 case 'L':
1223 hadError = !listviewcmd(&error);
1224 break;
1225
1226 case 'S':
1227 hadError = !SecPropertycmd(optarg, &error);
1228 break;
1229
1230 case 'b':
1231 hadError = setBag(optarg, &error);
1232 break;
1233
1234 case 'Y':
1235 hadError = dumpYetToSync(&error);
1236 break;
1237 case '?':
1238 default:
1239 return 2; /* Return 2 triggers usage message. */
1240 }
1241
1242 if (hadError)
1243 printerr(CFSTR("Error: %@\n"), error);
1244
1245 return result;
1246 }