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