1/* 2 * Copyright (c) 2000-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 24/* 25 * Modification History 26 * 27 * June 13, 2005 Allan Nathanson <ajn@apple.com> 28 * - added SCPreferences support 29 * 30 * August 4, 2004 Allan Nathanson <ajn@apple.com> 31 * - added network configuration (prefs) support 32 * 33 * September 25, 2002 Allan Nathanson <ajn@apple.com> 34 * - added command line history & editing 35 * 36 * July 9, 2001 Allan Nathanson <ajn@apple.com> 37 * - added "-r" option for checking network reachability 38 * - added "-w" option to check/wait for the presence of a 39 * dynamic store key. 40 * 41 * June 1, 2001 Allan Nathanson <ajn@apple.com> 42 * - public API conversion 43 * 44 * November 9, 2000 Allan Nathanson <ajn@apple.com> 45 * - initial revision 46 */ 47 48#include <TargetConditionals.h> 49#include <ctype.h> 50#include <getopt.h> 51#include <stdio.h> 52#include <stdlib.h> 53#include <string.h> 54#include <termios.h> 55#include <unistd.h> 56#include <sysexits.h> 57 58#ifdef DEBUG 59#include <mach/mach.h> 60#include <mach/mach_error.h> 61#endif /* DEBUG */ 62 63#include "scutil.h" 64#include "commands.h" 65#include "dictionary.h" 66#include "net.h" 67#include "nc.h" 68#include "prefs.h" 69#include "session.h" 70#include "tests.h" 71 72 73#define LINE_LENGTH 2048 74 75 76__private_extern__ AuthorizationRef authorization = NULL; 77__private_extern__ InputRef currentInput = NULL; 78__private_extern__ Boolean doDispatch = FALSE; 79__private_extern__ int nesting = 0; 80__private_extern__ CFRunLoopRef notifyRl = NULL; 81__private_extern__ CFRunLoopSourceRef notifyRls = NULL; 82__private_extern__ SCPreferencesRef prefs = NULL; 83__private_extern__ SCDynamicStoreRef store = NULL; 84__private_extern__ CFPropertyListRef value = NULL; 85__private_extern__ CFMutableArrayRef watchedKeys = NULL; 86__private_extern__ CFMutableArrayRef watchedPatterns = NULL; 87 88static const struct option longopts[] = { 89// { "debug", no_argument, NULL, 'd' }, 90// { "dispatch", no_argument, NULL, 'D' }, 91// { "verbose", no_argument, NULL, 'v' }, 92// { "SPI", no_argument, NULL, 'p' }, 93// { "check-reachability", required_argument, NULL, 'r' }, 94// { "timeout", required_argument, NULL, 't' }, 95// { "wait-key", required_argument, NULL, 'w' }, 96// { "watch-reachability", no_argument, NULL, 'W' }, 97 { "dns", no_argument, NULL, 0 }, 98 { "get", required_argument, NULL, 0 }, 99 { "error", required_argument, NULL, 0 }, 100 { "help", no_argument, NULL, '?' }, 101 { "nc", required_argument, NULL, 0 }, 102 { "net", no_argument, NULL, 0 }, 103 { "nwi", no_argument, NULL, 0 }, 104 { "prefs", no_argument, NULL, 0 }, 105 { "proxy", no_argument, NULL, 0 }, 106 { "renew", required_argument, NULL, 0 }, 107 { "set", required_argument, NULL, 0 }, 108 { "snapshot", no_argument, NULL, 0 }, 109 { "user", required_argument, NULL, 0 }, 110 { "password", required_argument, NULL, 0 }, 111 { "secret", required_argument, NULL, 0 }, 112 { "log", required_argument, NULL, 0 }, 113 { NULL, 0, NULL, 0 } 114}; 115 116 117__private_extern__ 118CFStringRef 119_copyStringFromSTDIN(CFStringRef prompt, CFStringRef defaultValue) 120{ 121 char buf[1024]; 122 int i; 123 Boolean is_user_prompt = (prompt != NULL && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)); 124 int len; 125 char *modbuf; 126 int modlen; 127 CFStringRef utf8; 128 129 /* Print out a prompt to user that entry is desired */ 130 if (is_user_prompt) { 131 if (defaultValue != NULL) { 132 SCPrint(TRUE, stdout, CFSTR("%@ [%@]: "), prompt, defaultValue); 133 } else { 134 SCPrint(TRUE, stdout, CFSTR("%@: "), prompt); 135 } 136 } 137 138 /* Get user input */ 139 if (fgets(buf, sizeof(buf), stdin) == NULL) { 140 return NULL; 141 } 142 143 /* Prepare for trim */ 144 len = (int)strlen(buf); 145 modbuf = buf; 146 modlen = len; 147 148 /* Trim new-line */ 149 if ((modlen > 0) && (modbuf[modlen - 1] == '\n')) { 150 modbuf[modlen - 1] = '\0'; 151 modlen--; 152 } 153 154 /* If nothing was entered at the user prompt, set default */ 155 if (is_user_prompt && defaultValue != NULL && modlen == 0) { 156 CFRetain(defaultValue); 157 return defaultValue; 158 } 159 160 /* Trim spaces from front */ 161 while (modlen > 0 && isspace(modbuf[0])) { 162 modbuf = &modbuf[1]; 163 modlen--; 164 } 165 166 /* Trim spaces from back */ 167 for (i = modlen - 1; i >= 0; i--) { 168 if (isspace(buf[i])) { 169 buf[i] = '\0'; 170 modlen--; 171 } else { 172 break; 173 } 174 } 175 176 utf8 = CFStringCreateWithBytes(NULL, (UInt8 *)modbuf, modlen, kCFStringEncodingUTF8, TRUE); 177 return utf8; 178} 179 180static char * 181getLine(char *buf, int len, InputRef src) 182{ 183 int n; 184 185 if (src->el) { 186 int count; 187 const char *line; 188 189 line = el_gets(src->el, &count); 190 if (line == NULL) 191 return NULL; 192 193 strncpy(buf, line, len); 194 } else { 195 if (fgets(buf, len, src->fp) == NULL) 196 return NULL; 197 } 198 199 n = (int)strlen(buf); 200 if (buf[n-1] == '\n') { 201 /* the entire line fit in the buffer, remove the newline */ 202 buf[n-1] = '\0'; 203 } else if (!src->el) { 204 /* eat the remainder of the line */ 205 do { 206 n = fgetc(src->fp); 207 } while ((n != '\n') && (n != EOF)); 208 } 209 210 if (src->h && (buf[0] != '\0')) { 211 HistEvent ev; 212 213 history(src->h, &ev, H_ENTER, buf); 214 } 215 216 217 return buf; 218} 219 220 221static char * 222getString(char **line) 223{ 224 char *s, *e, c, *string; 225 int i, isQuoted = 0, escaped = 0; 226 227 if (*line == NULL) return NULL; 228 if (**line == '\0') return NULL; 229 230 /* Skip leading white space */ 231 while (isspace(**line)) *line += 1; 232 233 /* Grab the next string */ 234 s = *line; 235 if (*s == '\0') { 236 return NULL; /* no string available */ 237 } else if (*s == '"') { 238 isQuoted = 1; /* it's a quoted string */ 239 s++; 240 } 241 242 for (e = s; (c = *e) != '\0'; e++) { 243 if (isQuoted && (c == '"')) 244 break; /* end of quoted string */ 245 if (c == '\\') { 246 e++; 247 if (*e == '\0') 248 break; /* if premature end-of-string */ 249 if ((*e == '"') || isspace(*e)) 250 escaped++; /* if escaped quote or white space */ 251 } 252 if (!isQuoted && isspace(c)) 253 break; /* end of non-quoted string */ 254 } 255 256 string = malloc(e - s - escaped + 1); 257 258 for (i = 0; s < e; s++) { 259 string[i] = *s; 260 if (!((s[0] == '\\') && ((s[1] == '"') || isspace(s[1])))) i++; 261 } 262 string[i] = '\0'; 263 264 if (isQuoted) 265 e++; /* move past end of quoted string */ 266 267 *line = e; 268 return string; 269} 270 271 272__private_extern__ 273Boolean 274process_line(InputRef src) 275{ 276 char *arg; 277 int argc = 0; 278 char **argv = NULL; 279 int i; 280 char line[LINE_LENGTH]; 281 char *s = line; 282 283 // if end-of-file, exit 284 if (getLine(line, sizeof(line), src) == NULL) 285 return FALSE; 286 287 if (nesting > 0) { 288 SCPrint(TRUE, stdout, CFSTR("%d> %s\n"), nesting, line); 289 } 290 291 // break up the input line 292 while ((arg = getString(&s)) != NULL) { 293 if (argc == 0) 294 argv = (char **)malloc(2 * sizeof(char *)); 295 else 296 argv = (char **)reallocf(argv, ((argc + 2) * sizeof(char *))); 297 argv[argc++] = arg; 298 } 299 300 if (argc == 0) { 301 return TRUE; // if no arguments 302 } 303 304 /* process the command */ 305 if (*argv[0] != '#') { 306 argv[argc] = NULL; // just in case... 307 currentInput = src; 308 do_command(argc, argv); 309 } 310 311 /* free the arguments */ 312 for (i = 0; i < argc; i++) { 313 free(argv[i]); 314 } 315 free(argv); 316 317 return !termRequested; 318} 319 320 321static void 322usage(const char *command) 323{ 324 SCPrint(TRUE, stderr, CFSTR("usage: %s\n"), command); 325 SCPrint(TRUE, stderr, CFSTR("\tinteractive access to the dynamic store.\n")); 326 SCPrint(TRUE, stderr, CFSTR("\n")); 327 SCPrint(TRUE, stderr, CFSTR(" or: %s --prefs [preference-file]\n"), command); 328 SCPrint(TRUE, stderr, CFSTR("\tinteractive access to the [raw] stored preferences.\n")); 329 SCPrint(TRUE, stderr, CFSTR("\n")); 330 SCPrint(TRUE, stderr, CFSTR(" or: %s [-W] -r nodename\n"), command); 331 SCPrint(TRUE, stderr, CFSTR(" or: %s [-W] -r address\n"), command); 332 SCPrint(TRUE, stderr, CFSTR(" or: %s [-W] -r local-address remote-address\n"), command); 333 SCPrint(TRUE, stderr, CFSTR("\tcheck reachability of node, address, or address pair (-W to \"watch\").\n")); 334 SCPrint(TRUE, stderr, CFSTR("\n")); 335 SCPrint(TRUE, stderr, CFSTR(" or: %s -w dynamic-store-key [ -t timeout ]\n"), command); 336 SCPrint(TRUE, stderr, CFSTR("\t-w\twait for presense of dynamic store key\n")); 337 SCPrint(TRUE, stderr, CFSTR("\t-t\ttime to wait for key\n")); 338 SCPrint(TRUE, stderr, CFSTR("\n")); 339 SCPrint(TRUE, stderr, CFSTR(" or: %s --get pref\n"), command); 340 SCPrint(TRUE, stderr, CFSTR(" or: %s --set pref [newval]\n"), command); 341 SCPrint(TRUE, stderr, CFSTR(" or: %s --get filename path key \n"), command); 342 SCPrint(TRUE, stderr, CFSTR("\tpref\tdisplay (or set) the specified preference. Valid preferences\n")); 343 SCPrint(TRUE, stderr, CFSTR("\t\tinclude:\n")); 344 SCPrint(TRUE, stderr, CFSTR("\t\t\tComputerName, LocalHostName, HostName\n")); 345 SCPrint(TRUE, stderr, CFSTR("\tnewval\tNew preference value to be set. If not specified,\n")); 346 SCPrint(TRUE, stderr, CFSTR("\t\tthe new value will be read from standard input.\n")); 347 SCPrint(TRUE, stderr, CFSTR("\n")); 348 SCPrint(TRUE, stderr, CFSTR(" or: %s --dns\n"), command); 349 SCPrint(TRUE, stderr, CFSTR("\tshow DNS configuration.\n")); 350 SCPrint(TRUE, stderr, CFSTR("\n")); 351 SCPrint(TRUE, stderr, CFSTR(" or: %s --proxy\n"), command); 352 SCPrint(TRUE, stderr, CFSTR("\tshow \"proxy\" configuration.\n")); 353 SCPrint(TRUE, stderr, CFSTR("\n")); 354 SCPrint(TRUE, stderr, CFSTR(" or: %s --nwi\n"), command); 355 SCPrint(TRUE, stderr, CFSTR("\tshow network information\n")); 356 SCPrint(TRUE, stderr, CFSTR("\n")); 357 SCPrint(TRUE, stderr, CFSTR(" or: %s --nc\n"), command); 358 SCPrint(TRUE, stderr, CFSTR("\tshow VPN network configuration information. Use --nc help for full command list\n")); 359 360 if (_sc_debug) { 361 SCPrint(TRUE, stderr, CFSTR("\n")); 362 SCPrint(TRUE, stderr, CFSTR(" or: %s --log IPMonitor [off|on]\n"), command); 363 SCPrint(TRUE, stderr, CFSTR("\tmanage logging.\n")); 364 } 365 366 if (getenv("ENABLE_EXPERIMENTAL_SCUTIL_COMMANDS")) { 367 SCPrint(TRUE, stderr, CFSTR("\n")); 368 SCPrint(TRUE, stderr, CFSTR(" or: %s --net\n"), command); 369 SCPrint(TRUE, stderr, CFSTR("\tmanage network configuration.\n")); 370 } 371 372 SCPrint(TRUE, stderr, CFSTR("\n")); 373 SCPrint(TRUE, stderr, CFSTR(" or: %s --error err#\n"), command); 374 SCPrint(TRUE, stderr, CFSTR("\tdisplay a descriptive message for the given error code\n")); 375 376 exit (EX_USAGE); 377} 378 379 380static char * 381prompt(EditLine *el) 382{ 383#if !TARGET_IPHONE_SIMULATOR 384 return "> "; 385#else // !TARGET_IPHONE_SIMULATOR 386 return "sim> "; 387#endif // !TARGET_IPHONE_SIMULATOR 388} 389 390 391int 392main(int argc, char * const argv[]) 393{ 394 Boolean doDNS = FALSE; 395 Boolean doNet = FALSE; 396 Boolean doNWI = FALSE; 397 Boolean doPrefs = FALSE; 398 Boolean doProxy = FALSE; 399 Boolean doReach = FALSE; 400 Boolean doSnap = FALSE; 401 char *error = NULL; 402 char *get = NULL; 403 char *log = NULL; 404 extern int optind; 405 int opt; 406 int opti; 407 const char *prog = argv[0]; 408 char *renew = NULL; 409 char *set = NULL; 410 char *nc_cmd = NULL; 411 InputRef src; 412 int timeout = 15; /* default timeout (in seconds) */ 413 char *wait = NULL; 414 Boolean watch = FALSE; 415 int xStore = 0; /* non dynamic store command line options */ 416 417 /* process any arguments */ 418 419 while ((opt = getopt_long(argc, argv, "dDvprt:w:W", longopts, &opti)) != -1) 420 switch(opt) { 421 case 'd': 422 _sc_debug = TRUE; 423 _sc_log = FALSE; /* enable framework logging */ 424 break; 425 case 'D': 426 doDispatch = TRUE; 427 break; 428 case 'v': 429 _sc_verbose = TRUE; 430 _sc_log = FALSE; /* enable framework logging */ 431 break; 432 case 'p': 433 enablePrivateAPI = TRUE; 434 break; 435 case 'r': 436 doReach = TRUE; 437 xStore++; 438 break; 439 case 't': 440 timeout = atoi(optarg); 441 break; 442 case 'w': 443 wait = optarg; 444 xStore++; 445 break; 446 case 'W': 447 watch = TRUE; 448 break; 449 case 0: 450 if (strcmp(longopts[opti].name, "dns") == 0) { 451 doDNS = TRUE; 452 xStore++; 453 } else if (strcmp(longopts[opti].name, "error") == 0) { 454 error = optarg; 455 xStore++; 456 } else if (strcmp(longopts[opti].name, "get") == 0) { 457 get = optarg; 458 xStore++; 459 } else if (strcmp(longopts[opti].name, "nc") == 0) { 460 nc_cmd = optarg; 461 xStore++; 462 } else if (strcmp(longopts[opti].name, "net") == 0) { 463 doNet = TRUE; 464 xStore++; 465 } else if (strcmp(longopts[opti].name, "nwi") == 0) { 466 doNWI = TRUE; 467 xStore++; 468 } else if (strcmp(longopts[opti].name, "prefs") == 0) { 469 doPrefs = TRUE; 470 xStore++; 471 } else if (strcmp(longopts[opti].name, "proxy") == 0) { 472 doProxy = TRUE; 473 xStore++; 474 } else if (strcmp(longopts[opti].name, "renew") == 0) { 475 renew = optarg; 476 xStore++; 477 } else if (strcmp(longopts[opti].name, "set") == 0) { 478 set = optarg; 479 xStore++; 480 } else if (strcmp(longopts[opti].name, "snapshot") == 0) { 481 doSnap = TRUE; 482 xStore++; 483 } else if (strcmp(longopts[opti].name, "log") == 0) { 484 log = optarg; 485 xStore++; 486 } else if (strcmp(longopts[opti].name, "user") == 0) { 487 username = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8); 488 } else if (strcmp(longopts[opti].name, "password") == 0) { 489 password = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8); 490 } else if (strcmp(longopts[opti].name, "secret") == 0) { 491 sharedsecret = CFStringCreateWithCString(NULL, optarg, kCFStringEncodingUTF8); 492 } 493 break; 494 case '?': 495 default : 496 usage(prog); 497 } 498 argc -= optind; 499 argv += optind; 500 501 if (xStore > 1) { 502 // if we are attempting to process more than one type of request 503 usage(prog); 504 } 505 506 /* are we checking (or watching) the reachability of a host/address */ 507 if (doReach) { 508 if (argc < 1) { 509 usage(prog); 510 } 511 if (watch) { 512 do_watchReachability(argc, (char **)argv); 513 } else { 514 do_checkReachability(argc, (char **)argv); 515 } 516 /* NOT REACHED */ 517 } 518 519 /* are we waiting on the presense of a dynamic store key */ 520 if (wait) { 521 do_wait(wait, timeout); 522 /* NOT REACHED */ 523 } 524 525 /* are we looking up the DNS configuration */ 526 if (doDNS) { 527 if (watch) { 528 do_watchDNSConfiguration(argc, (char **)argv); 529 } else { 530 do_showDNSConfiguration(argc, (char **)argv); 531 } 532 /* NOT REACHED */ 533 } 534 535 if (doNWI) { 536 if (watch) { 537 do_watchNWI(argc, (char**)argv); 538 } else { 539 do_showNWI(argc, (char**)argv); 540 } 541 /* NOT REACHED */ 542 } 543 544 if (doSnap) { 545 if (!enablePrivateAPI 546#if !TARGET_IPHONE_SIMULATOR 547 || (geteuid() != 0) 548#endif // !TARGET_IPHONE_SIMULATOR 549 ) { 550 usage(prog); 551 } 552 553 do_open(0, NULL); /* open the dynamic store */ 554 do_snapshot(argc, (char**)argv); 555 exit(0); 556 } 557 558 /* are we translating error #'s to descriptive text */ 559 if (error != NULL) { 560 int sc_status = atoi(error); 561 562 SCPrint(TRUE, stdout, CFSTR("Error: 0x%08x %d %s\n"), 563 sc_status, 564 sc_status, 565 SCErrorString(sc_status)); 566 exit(0); 567 } 568 569 /* are we looking up a preference value */ 570 if (get) { 571 if (argc != 2) { 572 if (findPref(get) < 0) { 573 usage(prog); 574 } 575 } else { 576 /* need to go back one argument 577 * for the filename */ 578 argc++; 579 argv--; 580 } 581 582 do_getPref(get, argc, (char **)argv); 583 /* NOT REACHED */ 584 } 585 586 /* are we looking up the proxy configuration */ 587 if (doProxy) { 588 do_showProxyConfiguration(argc, (char **)argv); 589 /* NOT REACHED */ 590 } 591 592 /* are we changing a preference value */ 593 if (set) { 594 if (findPref(set) < 0) { 595 usage(prog); 596 } 597 do_setPref(set, argc, (char **)argv); 598 /* NOT REACHED */ 599 } 600 601 /* verbose log */ 602 if (log != NULL) { 603 if (strcasecmp(log, "IPMonitor")) { 604 usage(prog); 605 } 606 do_log(log, argc, (char * *)argv); 607 /* NOT REACHED */ 608 } 609 610 /* network connection commands */ 611 if (nc_cmd) { 612 if (find_nc_cmd(nc_cmd) < 0) { 613 usage(prog); 614 } 615 do_nc_cmd(nc_cmd, argc, (char **)argv, watch); 616 /* NOT REACHED */ 617 } 618 619 if (doNet) { 620 /* if we are going to be managing the network configuration */ 621 commands = (cmdInfo *)commands_net; 622 nCommands = nCommands_net; 623 624 if (!getenv("ENABLE_EXPERIMENTAL_SCUTIL_COMMANDS")) { 625 usage(prog); 626 } 627 628 do_net_init(); /* initialization */ 629 do_net_open(argc, (char **)argv); /* open prefs */ 630 } else if (doPrefs) { 631 /* if we are going to be managing the network configuration */ 632 commands = (cmdInfo *)commands_prefs; 633 nCommands = nCommands_prefs; 634 635 do_dictInit(0, NULL); /* start with an empty dictionary */ 636 do_prefs_init(); /* initialization */ 637 do_prefs_open(argc, (char **)argv); /* open prefs */ 638 } else { 639 /* if we are going to be managing the dynamic store */ 640 commands = (cmdInfo *)commands_store; 641 nCommands = nCommands_store; 642 643 do_dictInit(0, NULL); /* start with an empty dictionary */ 644 do_open(0, NULL); /* open the dynamic store */ 645 } 646 647 /* are we trying to renew a DHCP lease */ 648 if (renew != NULL) { 649 do_renew(renew); 650 /* NOT REACHED */ 651 } 652 653 /* allocate command input stream */ 654 src = (InputRef)CFAllocatorAllocate(NULL, sizeof(Input), 0); 655 src->fp = stdin; 656 src->el = NULL; 657 src->h = NULL; 658 659 if (isatty(fileno(src->fp))) { 660 int editmode = 1; 661 HistEvent ev; 662 struct termios t; 663 664 if (tcgetattr(fileno(src->fp), &t) != -1) { 665 if ((t.c_lflag & ECHO) == 0) { 666 editmode = 0; 667 } 668 } 669 src->el = el_init(prog, src->fp, stdout, stderr); 670 src->h = history_init(); 671 672 (void)history(src->h, &ev, H_SETSIZE, INT_MAX); 673 el_set(src->el, EL_HIST, history, src->h); 674 675 if (!editmode) { 676 el_set(src->el, EL_EDITMODE, 0); 677 } 678 679 el_set(src->el, EL_EDITOR, "emacs"); 680 el_set(src->el, EL_PROMPT, prompt); 681 682 el_source(src->el, NULL); 683 684 if ((el_get(src->el, EL_EDITMODE, &editmode) != -1) && editmode != 0) { 685 el_set(src->el, EL_SIGNAL, 1); 686 } else { 687 history_end(src->h); 688 src->h = NULL; 689 el_end(src->el); 690 src->el = NULL; 691 } 692 } 693 694 while (TRUE) { 695 Boolean ok; 696 697 ok = process_line(src); 698 if (!ok) { 699 break; 700 } 701 } 702 703 /* close the socket, free resources */ 704 if (src->h) history_end(src->h); 705 if (src->el) el_end(src->el); 706 (void)fclose(src->fp); 707 CFAllocatorDeallocate(NULL, src); 708 709 exit (EX_OK); // insure the process exit status is 0 710 return 0; // ...and make main fit the ANSI spec. 711} 712