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 #include <getopt.h> 35 #include <readpassphrase.h> 36 37 #include <Security/SecItem.h> 38 39 #include <CoreFoundation/CFNumber.h> 40 #include <CoreFoundation/CFString.h> 41 42 #include <Security/SecureObjectSync/SOSCloudCircle.h> 43 #include <Security/SecureObjectSync/SOSCloudCircleInternal.h> 44 #include <Security/SecureObjectSync/SOSPeerInfo.h> 45 #include "keychain/SecureObjectSync/SOSPeerInfoPriv.h" 46 #include "keychain/SecureObjectSync/SOSPeerInfoV2.h" 47 #include "keychain/SecureObjectSync/SOSUserKeygen.h" 48 #include "keychain/SecureObjectSync/SOSKVSKeys.h" 49 #include "keychain/securityd/SOSCloudCircleServer.h" 50 #include <Security/SecureObjectSync/SOSBackupSliceKeyBag.h> 51 #include <Security/SecOTRSession.h> 52 #include "keychain/SecureObjectSync/CKBridge/SOSCloudKeychainClient.h" 53 54 #include <utilities/SecCFWrappers.h> 55 #include <utilities/debugging.h> 56 57 #include "SecurityTool/sharedTool/readline.h" 58 #include <notify.h> 59 60 #include "keychain_sync.h" 61 #include "keychain_log.h" 62 63 #include "secToolFileIO.h" 64 #include "secViewDisplay.h" 65 #include "accountCirclesViewsPrint.h" 66 67 #include <Security/SecPasswordGenerate.h> 68 69 #define MAXKVSKEYTYPE kUnknownKey 70 #define DATE_LENGTH 18 71 72 73 static bool clearAllKVS(CFErrorRef *error) 74 { 75 __block bool result = false; 76 const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC; 77 dispatch_queue_t processQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 78 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0); 79 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds); 80 81 secnotice("circleOps", "security tool called SOSCloudKeychainClearAll to clear KVS"); 82 SOSCloudKeychainClearAll(processQueue, ^(CFDictionaryRef returnedValues, CFErrorRef cerror) 83 { 84 result = (cerror != NULL); 85 dispatch_semaphore_signal(waitSemaphore); 86 }); 87 88 dispatch_semaphore_wait(waitSemaphore, finishTime); 89 90 return result; 91 } 92 93 static bool enableDefaultViews() 94 { 95 bool result = false; 96 CFMutableSetRef viewsToEnable = SOSViewCopyViewSet(kViewSetV0); 97 CFMutableSetRef viewsToDisable = CFSetCreateMutable(NULL, 0, NULL); 98 99 result = SOSCCViewSet(viewsToEnable, viewsToDisable); 100 CFRelease(viewsToEnable); 101 CFRelease(viewsToDisable); 102 return result; 103 } 104 105 static bool requestToJoinCircle(CFErrorRef *error) 106 { 107 // Set the visual state of switch based on membership in circle 108 bool hadError = false; 109 SOSCCStatus ccstatus = SOSCCThisDeviceIsInCircle(error); 110 111 switch (ccstatus) 112 { 113 case kSOSCCCircleAbsent: 114 hadError = !SOSCCResetToOffering(error); 115 hadError &= enableDefaultViews(); 116 break; 117 case kSOSCCNotInCircle: 118 hadError = !SOSCCRequestToJoinCircle(error); 119 hadError &= enableDefaultViews(); 120 break; 121 default: 122 printerr(CFSTR("Request to join circle with bad status: %@ (%d)\n"), SOSCCGetStatusDescription(ccstatus), ccstatus); 123 break; 124 } 125 return hadError; 126 } 127 128 static bool setPassword(char *labelAndPassword, CFErrorRef *err) 129 { 130 char *last = NULL; 131 char *token0 = strtok_r(labelAndPassword, ":", &last); 132 char *token1 = strtok_r(NULL, "", &last); 133 CFStringRef label = token1 ? CFStringCreateWithCString(NULL, token0, kCFStringEncodingUTF8) : CFSTR("security command line tool"); 134 char *password_token = token1 ? token1 : token0; 135 password_token = password_token ? password_token : ""; 136 CFDataRef password = CFDataCreate(NULL, (const UInt8*) password_token, strlen(password_token)); 137 bool returned = !SOSCCSetUserCredentials(label, password, err); 138 CFRelease(label); 139 CFRelease(password); 140 return returned; 141 } 142 143 static bool tryPassword(char *labelAndPassword, CFErrorRef *err) 144 { 145 char *last = NULL; 146 char *token0 = strtok_r(labelAndPassword, ":", &last); 147 char *token1 = strtok_r(NULL, "", &last); 148 CFStringRef label = token1 ? CFStringCreateWithCString(NULL, token0, kCFStringEncodingUTF8) : CFSTR("security command line tool"); 149 char *password_token = token1 ? token1 : token0; 150 password_token = password_token ? password_token : ""; 151 CFDataRef password = CFDataCreate(NULL, (const UInt8*) password_token, strlen(password_token)); 152 bool returned = !SOSCCTryUserCredentials(label, password, err); 153 CFRelease(label); 154 CFRelease(password); 155 return returned; 156 } 157 158 /* 159 * Prompt user, call SOSCCTryUserCredentials. 160 * Does not support optional label syntax like -T/-P. 161 * Returns true on success. 162 */ 163 static bool 164 promptAndTryPassword(CFErrorRef *error) 165 { 166 bool success = false; 167 char passbuf[1024]; 168 CFDataRef password; 169 170 if (readpassphrase("iCloud password: ", passbuf, sizeof(passbuf), RPP_REQUIRE_TTY) != NULL) { 171 password = CFDataCreate(NULL, (const UInt8 *)passbuf, strlen(passbuf)); 172 if (password != NULL) { 173 success = SOSCCTryUserCredentials(CFSTR("security command line tool"), password, error); 174 CFReleaseNull(password); 175 } 176 } 177 178 return success; 179 } 180 181 static bool syncAndWait(CFErrorRef *err) 182 { 183 __block CFTypeRef objects = NULL; 184 185 dispatch_queue_t generalq = dispatch_queue_create("general", DISPATCH_QUEUE_SERIAL); 186 187 const uint64_t maxTimeToWaitInSeconds = 30ull * NSEC_PER_SEC; 188 dispatch_semaphore_t waitSemaphore = dispatch_semaphore_create(0); 189 dispatch_time_t finishTime = dispatch_time(DISPATCH_TIME_NOW, maxTimeToWaitInSeconds); 190 191 CloudKeychainReplyBlock replyBlock = ^ (CFDictionaryRef returnedValues, CFErrorRef error) 192 { 193 secinfo("sync", "SOSCloudKeychainSynchronizeAndWait returned: %@", returnedValues); 194 if (error) 195 secerror("SOSCloudKeychainSynchronizeAndWait returned error: %@", error); 196 objects = CFRetainSafe(returnedValues); 197 198 secinfo("sync", "SOSCloudKeychainGetObjectsFromCloud block exit: %@", objects); 199 dispatch_semaphore_signal(waitSemaphore); 200 }; 201 202 SOSCloudKeychainSynchronizeAndWait(generalq, replyBlock); 203 204 dispatch_semaphore_wait(waitSemaphore, finishTime); 205 206 (void)SOSCCDumpCircleKVSInformation(NULL); 207 fprintf(outFile, "\n"); 208 return false; 209 } 210 211 static void dumpStringSet(CFStringRef label, CFSetRef s) { 212 if(!s || !label) return; 213 214 printmsg(CFSTR("%@: { "), label); 215 __block bool first = true; 216 CFSetForEach(s, ^(const void *p) { 217 CFStringRef fmt = CFSTR(", %@"); 218 if(first) { 219 fmt = CFSTR("%@"); 220 } 221 CFStringRef string = (CFStringRef) p; 222 printmsg(fmt, string); 223 first=false; 224 }); 225 printmsg(CFSTR(" }\n"), NULL); 226 } 227 228 static bool dumpMyPeer(CFErrorRef *error) { 229 SOSPeerInfoRef myPeer = SOSCCCopyMyPeerInfo(error); 230 231 if (!myPeer) return false; 232 233 CFStringRef peerID = SOSPeerInfoGetPeerID(myPeer); 234 CFStringRef peerName = SOSPeerInfoGetPeerName(myPeer); 235 CFIndex peerVersion = SOSPeerInfoGetVersion(myPeer); 236 bool retirement = SOSPeerInfoIsRetirementTicket(myPeer); 237 238 printmsg(CFSTR("Peer Name: %@ PeerID: %@ Version: %d\n"), peerName, peerID, peerVersion); 239 if(retirement) { 240 CFDateRef retdate = SOSPeerInfoGetRetirementDate(myPeer); 241 printmsg(CFSTR("Retired: %@\n"), retdate); 242 243 } 244 245 if(peerVersion >= 2) { 246 CFMutableSetRef views = SOSPeerInfoV2DictionaryCopySet(myPeer, sViewsKey); 247 CFStringRef serialNumber = SOSPeerInfoV2DictionaryCopyString(myPeer, sSerialNumberKey); 248 CFBooleanRef preferIDS = SOSPeerInfoV2DictionaryCopyBoolean(myPeer, sPreferIDS); 249 CFBooleanRef preferIDSFragmentation = SOSPeerInfoV2DictionaryCopyBoolean(myPeer, sPreferIDSFragmentation); 250 CFBooleanRef preferIDSACKModel = SOSPeerInfoV2DictionaryCopyBoolean(myPeer, sPreferIDSACKModel); 251 CFStringRef transportType = SOSPeerInfoV2DictionaryCopyString(myPeer, sTransportType); 252 CFStringRef idsDeviceID = SOSPeerInfoV2DictionaryCopyString(myPeer, sDeviceID); 253 254 printmsg(CFSTR("Serial#: %@ PrefIDS#: %@ PrefFragmentation#: %@ PrefACK#: %@ transportType#: %@ idsDeviceID#: %@\n"), 255 serialNumber, preferIDS, preferIDSFragmentation, preferIDSACKModel, transportType, idsDeviceID); 256 257 printmsg(CFSTR("Serial#: %@\n"), 258 serialNumber); 259 dumpStringSet(CFSTR(" Views: "), views); 260 261 262 CFReleaseSafe(serialNumber); 263 CFReleaseSafe(preferIDS); 264 CFReleaseSafe(preferIDSFragmentation); 265 CFReleaseSafe(views); 266 CFReleaseSafe(transportType); 267 CFReleaseSafe(idsDeviceID); 268 } 269 270 bool ret = myPeer != NULL; 271 CFReleaseNull(myPeer); 272 return ret; 273 } 274 275 static bool setBag(char *itemName, CFErrorRef *err) 276 { 277 __block bool success = false; 278 __block CFErrorRef error = NULL; 279 280 CFStringRef random = SecPasswordCreateWithRandomDigits(10, NULL); 281 282 CFStringPerformWithUTF8CFData(random, ^(CFDataRef stringAsData) { 283 if (0 == strncasecmp(optarg, "single", 6) || 0 == strncasecmp(optarg, "all", 3)) { 284 bool includeV0 = (0 == strncasecmp(optarg, "all", 3)); 285 printmsg(CFSTR("Setting iCSC single using entropy from string: %@\n"), random); 286 CFDataRef aks_bag = SecAKSCopyBackupBagWithSecret(CFDataGetLength(stringAsData), (uint8_t*)CFDataGetBytePtr(stringAsData), &error); 287 288 if (aks_bag) { 289 success = SOSCCRegisterSingleRecoverySecret(aks_bag, includeV0, &error); 290 if (!success) { 291 printmsg(CFSTR("Failed registering single secret %@"), error); 292 CFReleaseNull(aks_bag); 293 } 294 } else { 295 printmsg(CFSTR("Failed to create aks_bag: %@"), error); 296 } 297 CFReleaseNull(aks_bag); 298 } else if (0 == strncasecmp(optarg, "device", 6)) { 299 printmsg(CFSTR("Setting Device Secret using entropy from string: %@\n"), random); 300 301 SOSPeerInfoRef me = SOSCCCopyMyPeerWithNewDeviceRecoverySecret(stringAsData, &error); 302 303 success = me != NULL; 304 305 if (!success) 306 printmsg(CFSTR("Failed: %@\n"), err); 307 CFReleaseNull(me); 308 } else { 309 printmsg(CFSTR("Unrecognized argument to -b %s\n"), optarg); 310 } 311 }); 312 313 314 return success; 315 } 316 317 static void prClientViewState(char *label, bool result) { 318 fprintf(outFile, "Sync Status for %s: %s\n", label, (result) ? "enabled": "not enabled"); 319 } 320 321 static bool clientViewStatus(CFErrorRef *error) { 322 prClientViewState("KeychainV0", SOSCCIsIcloudKeychainSyncing()); 323 prClientViewState("Safari", SOSCCIsSafariSyncing()); 324 prClientViewState("AppleTV", SOSCCIsAppleTVSyncing()); 325 prClientViewState("HomeKit", SOSCCIsHomeKitSyncing()); 326 prClientViewState("Wifi", SOSCCIsWiFiSyncing()); 327 prClientViewState("AlwaysOnNoInitialSync", SOSCCIsContinuityUnlockSyncing()); 328 return false; 329 } 330 331 #pragma mark - 332 #pragma mark --remove-peer 333 334 static void 335 add_matching_peerinfos(CFMutableArrayRef list, CFArrayRef spids, CFArrayRef (*copy_peer_func)(CFErrorRef *)) 336 { 337 CFErrorRef error; 338 CFArrayRef peers; 339 SOSPeerInfoRef pi; 340 CFStringRef spid; 341 CFIndex i, j; 342 343 peers = copy_peer_func(&error); 344 if (peers != NULL) { 345 for (i = 0; i < CFArrayGetCount(peers); i++) { 346 pi = (SOSPeerInfoRef)CFArrayGetValueAtIndex(peers, i); 347 for (j = 0; j < CFArrayGetCount(spids); j++) { 348 spid = (CFStringRef)CFArrayGetValueAtIndex(spids, j); 349 if (CFStringGetLength(spid) < 8) { 350 continue; 351 } 352 if (CFStringHasPrefix(SOSPeerInfoGetPeerID(pi), spid)) { 353 CFArrayAppendValue(list, pi); 354 } 355 } 356 } 357 CFRelease(peers); 358 } else { 359 // unlikely 360 CFShow(error); 361 CFRelease(error); 362 } 363 } 364 365 static CFArrayRef 366 copy_peerinfos(CFArrayRef spids) 367 { 368 CFMutableArrayRef matches; 369 370 matches = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 371 add_matching_peerinfos(matches, spids, SOSCCCopyValidPeerPeerInfo); 372 add_matching_peerinfos(matches, spids, SOSCCCopyNotValidPeerPeerInfo); 373 add_matching_peerinfos(matches, spids, SOSCCCopyRetirementPeerInfo); 374 375 return matches; 376 } 377 378 static bool 379 doRemovePeers(CFArrayRef peerids, CFErrorRef *error) 380 { 381 bool success = false; 382 CFArrayRef peers = NULL; 383 CFErrorRef localError = NULL; 384 CFIndex i; 385 char buf[16]; 386 387 peers = copy_peerinfos(peerids); 388 if (peers == NULL || CFArrayGetCount(peers) == 0) { 389 fprintf(stdout, "No matching peers to remove.\n"); 390 success = true; 391 goto done; 392 } 393 394 fprintf(stdout, "Matched the following devices:\n"); 395 for (i = 0; i < CFArrayGetCount(peers); i++) { 396 // Ugly. 397 CFShow(CFArrayGetValueAtIndex(peers, i)); 398 } 399 400 if (readpassphrase("Confirm removal (y/N): ", buf, sizeof(buf), RPP_ECHO_ON | RPP_FORCEUPPER) == NULL) { 401 goto done; 402 } 403 404 if (buf[0] != 'Y') { 405 success = true; 406 goto done; 407 } 408 409 success = SOSCCRemovePeersFromCircle(peers, &localError); 410 if (!success && isSOSErrorCoded(localError, kSOSErrorPrivateKeyAbsent)) { 411 CFReleaseNull(localError); 412 413 success = promptAndTryPassword(&localError); 414 if (success) { 415 success = SOSCCRemovePeersFromCircle(peers, &localError); 416 } 417 } 418 419 done: 420 CFReleaseNull(peers); 421 422 if (!success && error != NULL) { 423 *error = localError; 424 } else { 425 CFReleaseNull(localError); 426 } 427 428 return success; 429 } 430 431 #pragma mark - 432 433 // enable, disable, accept, reject, status, Reset, Clear 434 int 435 keychain_sync(int argc, char * const *argv) 436 { 437 /* 438 "Keychain Syncing" 439 " -d disable" 440 " -e enable (join/create circle)" 441 " -i info (current status)" 442 " -m dump my peer" 443 " 444 "Account/Circle Management" 445 " -a accept all applicants" 446 " -r reject all applicants" 447 " -b device|all|single Register a backup bag - THIS RESETS BACKUPS!\n" 448 449 " -N (re-)set to new account (USE WITH CARE: device will not leave circle before resetting account!)" 450 " -O reset to offering" 451 " -R reset circle" 452 " -o list view unaware peers in circle" 453 " -0 boot view unaware peers from circle" 454 " -5 cleanup old KVS keys in KVS" 455 " 456 "Circle Tools\n" 457 " --remove-peer SPID Remove a peer identified by the first 8 or more\n" 458 " characters of its spid. Specify multiple times to\n" 459 " remove more than one peer.\n" 460 "Password" 461 " -P [label:]password set password (optionally for a given label) for sync" 462 " -T [label:]password try password (optionally for a given label) for sync" 463 " 464 "KVS" 465 " -k pend all registered kvs keys" 466 " -C clear all values from KVS" 467 " -D [itemName] dump contents of KVS" 468 " -W sync and dump" 469 " 470 "Misc" 471 " -v [enable|disable|query:viewname] enable, disable, or query my PeerInfo's view set" 472 " viewnames are: keychain|masterkey|iclouddrive|photos|cloudkit|escrow|fde|maildrop|icloudbackup|notes|imessage|appletv|homekit|" 473 " wifi|passwords|creditcards|icloudidentity|othersyncable" 474 " -L list all known view and their status" 475 " -U purge private key material cache\n" 476 " -V Report View Sync Status on all known clients.\n" 477 */ 478 enum { 479 SYNC_REMOVE_PEER, 480 }; 481 int action = -1; 482 const struct option longopts[] = { 483 { "remove-peer", required_argument, &action, SYNC_REMOVE_PEER, }, 484 { NULL, 0, NULL, 0, }, 485 }; 486 int ch, result = 0; 487 CFErrorRef error = NULL; 488 bool hadError = false; 489 CFMutableArrayRef peers2remove = NULL; 490 SOSLogSetOutputTo(NULL, NULL); 491 492 while ((ch = getopt_long(argc, argv, "ab:deikmorv:NCDLOP:RT:UWV05", longopts, NULL)) != -1) { 493 switch (ch) { 494 case 'a': 495 { 496 CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL); 497 if (applicants) { 498 hadError = !SOSCCAcceptApplicants(applicants, &error); 499 CFRelease(applicants); 500 } else { 501 fprintf(errFile, "No applicants to accept\n"); 502 } 503 break; 504 } 505 case 'b': 506 { 507 hadError = setBag(optarg, &error); 508 break; 509 } 510 case 'd': 511 { 512 fprintf(outFile, "Turning OFF keychain syncing\n"); 513 hadError = !SOSCCRemoveThisDeviceFromCircle(&error); 514 break; 515 } 516 case 'e': 517 { 518 fprintf(outFile, "Turning ON keychain syncing\n"); 519 hadError = requestToJoinCircle(&error); 520 break; 521 } 522 case 'i': 523 { 524 SOSCCDumpCircleInformation(); 525 SOSCCDumpEngineInformation(); 526 break; 527 } 528 case 'k': 529 { 530 notify_post("com.apple.security.cloudkeychain.forceupdate"); 531 break; 532 } 533 case 'm': 534 { 535 hadError = !dumpMyPeer(&error); 536 break; 537 } 538 case 'o': 539 { 540 SOSCCDumpViewUnwarePeers(); 541 break; 542 } 543 case 'r': 544 { 545 CFArrayRef applicants = SOSCCCopyApplicantPeerInfo(NULL); 546 if (applicants) { 547 hadError = !SOSCCRejectApplicants(applicants, &error); 548 CFRelease(applicants); 549 } else { 550 fprintf(errFile, "No applicants to reject\n"); 551 } 552 break; 553 } 554 case 'v': 555 { 556 hadError = !viewcmd(optarg, &error); 557 break; 558 } 559 case 'C': 560 { 561 hadError = clearAllKVS(&error); 562 break; 563 } 564 case 'D': 565 { 566 (void)SOSCCDumpCircleKVSInformation(optarg); 567 break; 568 } 569 case 'L': 570 { 571 hadError = !listviewcmd(&error); 572 break; 573 } 574 case 'N': 575 { 576 hadError = !SOSCCAccountSetToNew(&error); 577 if (!hadError) 578 notify_post(kSOSCCCircleChangedNotification); 579 break; 580 } 581 case 'O': 582 { 583 hadError = !SOSCCResetToOffering(&error); 584 break; 585 } 586 case 'P': 587 { 588 hadError = setPassword(optarg, &error); 589 break; 590 } 591 case 'R': 592 { 593 hadError = !SOSCCResetToEmpty(&error); 594 break; 595 } 596 case 'T': 597 { 598 hadError = tryPassword(optarg, &error); 599 break; 600 } 601 case 'U': 602 { 603 hadError = !SOSCCPurgeUserCredentials(&error); 604 break; 605 } 606 case 'V': 607 { 608 hadError = clientViewStatus(&error); 609 break; 610 } 611 case 'W': 612 { 613 hadError = syncAndWait(&error); 614 break; 615 } 616 case '0': 617 { 618 CFArrayRef unawares = SOSCCCopyViewUnawarePeerInfo(&error); 619 if (unawares) { 620 hadError = !SOSCCRemovePeersFromCircle(unawares, &error); 621 } else { 622 hadError = true; 623 } 624 CFReleaseNull(unawares); 625 break; 626 } 627 case '5' : 628 { 629 bool result = SOSCCCleanupKVSKeys(&error); 630 if(result) 631 { 632 printmsg(CFSTR("Got all the keys from KVS %d\n"), result); 633 }else { 634 hadError = true; 635 } 636 break; 637 } 638 case 0: 639 { 640 if (action == SYNC_REMOVE_PEER) { 641 CFStringRef optstr = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8); 642 if (peers2remove == NULL) { 643 peers2remove = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 644 } 645 CFArrayAppendValue(peers2remove, optstr); 646 CFReleaseNull(optstr); 647 } else { 648 return SHOW_USAGE_MESSAGE; 649 } 650 break; 651 } 652 case '?': 653 default: 654 return SHOW_USAGE_MESSAGE; 655 } 656 } 657 658 if (peers2remove != NULL) { 659 hadError = !doRemovePeers(peers2remove, &error); 660 CFRelease(peers2remove); 661 } 662 663 if (hadError) { 664 printerr(CFSTR("Error: %@\n"), error); 665 } 666 667 return result; 668 }