]> git.saurik.com Git - apple/configd.git/blob - scutil.tproj/scutil.c
a476ff9cc7aa7e7a481b0c00bff06fb90fc7021c
[apple/configd.git] / scutil.tproj / scutil.c
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
88 static 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__
118 CFStringRef
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
180 static char *
181 getLine(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
221 static char *
222 getString(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__
273 Boolean
274 process_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
321 static void
322 usage(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
380 static char *
381 prompt(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
391 int
392 main(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 }