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