1/*
2 * Copyright (c) 2003-2010,2013 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 * security.c
24 */
25
26#include "SecurityTool.h"
27
28#include <SecurityTool/readline.h>
29
30#include "leaks.h"
31
32#include <ctype.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <unistd.h>
37
38#include <CoreFoundation/CFRunLoop.h>
39
40#if NO_SERVER
41#include <securityd/spi.h>
42#endif
43
44/* Maximum length of an input line in interactive mode. */
45#define MAX_LINE_LEN 4096
46/* Maximum number of arguments on an input line in interactive mode. */
47#define MAX_ARGS 32
48
49
50/* The default prompt. */
51const char *prompt_string = "security> ";
52
53/* The name of this program. */
54const char *prog_name;
55
56
57/* Forward declarations of static functions. */
58
59
60/* Global variables. */
61int do_quiet = 0;
62int do_verbose = 0;
63
64/* Return 1 if name matches command. */
65static int
66match_command(const char *command_name, const char *name)
67{
68	return !strncmp(command_name, name, strlen(name));
69}
70
71/* The help command. */
72int
73help(int argc, char * const *argv)
74{
75	const command *c;
76
77	if (argc > 1)
78	{
79		char * const *arg;
80		for (arg = argv + 1; *arg; ++arg)
81		{
82			int found = 0;
83
84			for (c = commands; c->c_name; ++c)
85			{
86				if (match_command(c->c_name, *arg))
87				{
88					found = 1;
89					break;
90				}
91			}
92
93			if (found)
94				printf("Usage: %s %s\n", c->c_name, c->c_usage);
95			else
96			{
97				fprintf(stderr, "%s: no such command: %s", argv[0], *arg);
98				return 1;
99			}
100		}
101	}
102	else
103	{
104		for (c = commands; c->c_name; ++c)
105			printf("    %-17s %s\n", c->c_name, c->c_help);
106	}
107
108	return 0;
109}
110
111/* States for split_line parser. */
112typedef enum
113{
114	SKIP_WS,
115	READ_ARG,
116	READ_ARG_ESCAPED,
117	QUOTED_ARG,
118	QUOTED_ARG_ESCAPED
119} parse_state;
120
121/* Split a line into multiple arguments and return them in *pargc and *pargv. */
122static void
123split_line(char *line, int *pargc, char * const **pargv)
124{
125	static char *argvec[MAX_ARGS + 1];
126	int argc = 0;
127	char *ptr = line;
128	char *dst = line;
129	parse_state state = SKIP_WS;
130	int quote_ch = 0;
131
132	for (ptr = line; *ptr; ++ptr)
133	{
134		if (state == SKIP_WS)
135		{
136			if (isspace(*ptr))
137				continue;
138
139			if (*ptr == '"' || *ptr == '\'')
140			{
141				quote_ch = *ptr;
142				state = QUOTED_ARG;
143				argvec[argc] = dst;
144				continue; /* Skip the quote. */
145			}
146			else
147			{
148				state = READ_ARG;
149				argvec[argc] = dst;
150			}
151		}
152
153		if (state == READ_ARG)
154		{
155			if (*ptr == '\\')
156			{
157				state = READ_ARG_ESCAPED;
158				continue;
159			}
160			else if (isspace(*ptr))
161			{
162				/* 0 terminate each arg. */
163				*dst++ = '\0';
164				argc++;
165				state = SKIP_WS;
166				if (argc >= MAX_ARGS)
167					break;
168			}
169			else
170				*dst++ = *ptr;
171		}
172
173		if (state == QUOTED_ARG)
174		{
175			if (*ptr == '\\')
176			{
177				state = QUOTED_ARG_ESCAPED;
178				continue;
179			}
180			if (*ptr == quote_ch)
181			{
182				/* 0 terminate each arg. */
183				*dst++ = '\0';
184				argc++;
185				state = SKIP_WS;
186				if (argc >= MAX_ARGS)
187					break;
188			}
189			else
190				*dst++ = *ptr;
191		}
192
193		if (state == READ_ARG_ESCAPED)
194		{
195			*dst++ = *ptr;
196			state = READ_ARG;
197		}
198
199		if (state == QUOTED_ARG_ESCAPED)
200		{
201			*dst++ = *ptr;
202			state = QUOTED_ARG;
203		}
204	}
205
206	if (state != SKIP_WS)
207	{
208		/* Terminate last arg. */
209		*dst++ = '\0';
210		argc++;
211	}
212
213	/* Teminate arg vector. */
214	argvec[argc] = NULL;
215
216	*pargv = argvec;
217	*pargc = argc;
218}
219
220/* Print a (hopefully) useful usage message. */
221static int
222usage(void)
223{
224	printf(
225		"Usage: %s [-h] [-i] [-l] [-p prompt] [-q] [-v] [command] [opt ...]\n"
226		"    -i    Run in interactive mode.\n"
227		"    -l    Run /usr/bin/leaks -nocontext before exiting.\n"
228		"    -p    Set the prompt to \"prompt\" (implies -i).\n"
229		"    -q    Be less verbose.\n"
230		"    -v    Be more verbose about what's going on.\n"
231		"%s commands are:\n", prog_name, prog_name);
232	help(0, NULL);
233	return 2;
234}
235
236/* Execute a single command. */
237static int
238execute_command(int argc, char * const *argv)
239{
240	const command *c;
241	int found = 0;
242
243	/* Nothing to do. */
244	if (argc == 0)
245		return 0;
246
247	for (c = commands; c->c_name; ++c)
248	{
249		if (match_command(c->c_name, argv[0]))
250		{
251			found = 1;
252			break;
253		}
254	}
255
256	if (found)
257	{
258		int result;
259
260		/* Reset getopt for command proc. */
261		optind = 1;
262		optreset = 1;
263
264		if (do_verbose)
265		{
266			int ix;
267
268			fprintf(stderr, "%s", c->c_name);
269			for (ix = 1; ix < argc; ++ix)
270				fprintf(stderr, " \"%s\"", argv[ix]);
271			fprintf(stderr, "\n");
272		}
273
274		result = c->c_func(argc, argv);
275		if (result == 2)
276			fprintf(stderr, "Usage: %s %s\n        %s\n", c->c_name, c->c_usage, c->c_help);
277
278		return result;
279	}
280	else
281	{
282		fprintf(stderr, "unknown command \"%s\"", argv[0]);
283		return 1;
284	}
285}
286
287static void
288receive_notifications(void)
289{
290	/* Run the CFRunloop to get any pending notifications. */
291	while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource);
292}
293
294int
295main(int argc, char * const *argv)
296{
297	int result = 0;
298	int do_help = 0;
299	int do_interactive = 0;
300	int do_leaks = 0;
301	int ch;
302
303	/* Remember my name. */
304	prog_name = strrchr(argv[0], '/');
305	prog_name = prog_name ? prog_name + 1 : argv[0];
306
307	/* Do getopt stuff for global options. */
308	optind = 1;
309	optreset = 1;
310	while ((ch = getopt(argc, argv, "hilp:qv")) != -1)
311	{
312		switch  (ch)
313		{
314		case 'h':
315			do_help = 1;
316			break;
317		case 'i':
318			do_interactive = 1;
319			break;
320		case 'l':
321			do_leaks = 1;
322			break;
323		case 'p':
324			do_interactive = 1;
325			prompt_string = optarg;
326			break;
327		case 'q':
328			do_quiet = 1;
329			break;
330		case 'v':
331			do_verbose = 1;
332			break;
333		case '?':
334		default:
335			return usage();
336		}
337	}
338
339	argc -= optind;
340	argv += optind;
341
342#if NO_SERVER
343# if DEBUG
344//    securityd_init();
345# endif
346#endif
347
348	if (do_help)
349	{
350		/* Munge argc/argv so that argv[0] is something. */
351		return help(argc + 1, argv - 1);
352	}
353	else if (argc > 0)
354	{
355		receive_notifications();
356		result = execute_command(argc, argv);
357		receive_notifications();
358	}
359	else if (do_interactive)
360	{
361		/* In interactive mode we just read commands and run them until readline returns NULL. */
362
363        /* Only show prompt string if stdin is a tty. */
364        int show_prompt = isatty(0);
365
366		for (;;)
367		{
368			static char buffer[MAX_LINE_LEN];
369			char * const *av, *input;
370			int ac;
371
372            if (show_prompt)
373                fprintf(stderr, "%s", prompt_string);
374
375			input = readline(buffer, MAX_LINE_LEN);
376			if (!input)
377				break;
378
379			split_line(input, &ac, &av);
380			receive_notifications();
381			result = execute_command(ac, av);
382			receive_notifications();
383			if (result == -1)
384			{
385				result = 0;
386				break;
387			}
388
389			if (result && ! do_quiet)
390			{
391				fprintf(stderr, "%s: returned %d\n", av[0], result);
392			}
393		}
394	}
395	else
396		result = usage();
397
398	if (do_leaks)
399	{
400		char *const argvec[3] = { "leaks", "-nocontext", NULL };
401		leaks(2, argvec);
402	}
403
404	return result;
405}
406