1/* Authors: Karl MacMillan <kmacmillan@tresys.com>
2 *          Joshua Brindle <jbrindle@tresys.com>
3 *          Jason Tang <jtang@tresys.com>
4 *
5 * Copyright (C) 2004-2005 Tresys Technology, LLC
6 *      This program is free software; you can redistribute it and/or
7 *      modify it under the terms of the GNU General Public License as
8 *      published by the Free Software Foundation, version 2.
9 */
10
11#include <fcntl.h>
12#include <getopt.h>
13#include <signal.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <errno.h>
17#include <string.h>
18#include <unistd.h>
19#include <sys/mman.h>
20#include <sys/stat.h>
21#include <sys/types.h>
22#include <libgen.h>
23
24#include <semanage/modules.h>
25
26enum client_modes {
27	NO_MODE, INSTALL_M, REMOVE_M,
28	LIST_M, RELOAD, PRIORITY_M, ENABLE_M, DISABLE_M
29};
30/* list of modes in which one ought to commit afterwards */
31static const int do_commit[] = {
32	0, 1, 1,
33	0, 0, 0, 1, 1,
34};
35
36struct command {
37	enum client_modes mode;
38	char *arg;
39};
40static struct command *commands = NULL;
41static int num_commands = 0;
42
43/* options given on command line */
44static int verbose;
45static int reload;
46static int no_reload;
47static int build;
48static int disable_dontaudit;
49static int preserve_tunables;
50static int ignore_module_cache;
51static uint16_t priority;
52
53static semanage_handle_t *sh = NULL;
54static char *store;
55static char *store_root;
56
57extern char *optarg;
58extern int optind;
59
60static void cleanup(void)
61{
62	while (--num_commands >= 0) {
63		free(commands[num_commands].arg);
64	}
65	free(commands);
66}
67
68/* Signal handlers. */
69static void handle_signal(int sig_num)
70{
71	if (sig_num == SIGINT || sig_num == SIGQUIT || sig_num == SIGTERM) {
72		/* catch these signals, and then drop them */
73	}
74}
75
76static void set_store(char *storename)
77{
78	/* For now this only supports a store name, later on this
79	 * should support an address for a remote connection */
80
81	if ((store = strdup(storename)) == NULL) {
82		fprintf(stderr, "Out of memory!\n");
83		goto bad;
84	}
85
86	return;
87
88      bad:
89	cleanup();
90	exit(1);
91}
92
93static void set_store_root(char *path)
94{
95	if ((store_root = strdup(path)) == NULL) {
96		fprintf(stderr, "Out of memory!\n");
97		goto bad;
98	}
99
100	return;
101
102      bad:
103	cleanup();
104	exit(1);
105}
106
107/* Establish signal handlers for the process. */
108static void create_signal_handlers(void)
109{
110	if (signal(SIGINT, handle_signal) == SIG_ERR ||
111	    signal(SIGQUIT, handle_signal) == SIG_ERR ||
112	    signal(SIGTERM, handle_signal) == SIG_ERR) {
113		fprintf(stderr, "Could not set up signal handler.\n");
114		exit(255);
115	}
116}
117
118static void usage(char *progname)
119{
120	printf("usage:  %s [options]... MODE [MODES]...\n", progname);
121	printf("Manage SELinux policy modules.\n");
122	printf("MODES:\n");
123	printf("  -R, --reload		    reload policy\n");
124	printf("  -B, --build		    build and reload policy\n");
125	printf("  -i,--install=MODULE_PKG   install a new module\n");
126	printf("  -r,--remove=MODULE_NAME   remove existing module\n");
127	printf("  -l,--list-modules=[KIND]  display list of installed modules\n");
128	printf("     KIND:  standard  list highest priority, enabled modules\n");
129	printf("            full      list all modules\n");
130	printf("  -X,--priority=PRIORITY    set priority for following operations (1-999)\n");
131	printf("  -e,--enable=MODULE_NAME   enable module\n");
132	printf("  -d,--disable=MODULE_NAME  disable module\n");
133	printf("Other options:\n");
134	printf("  -s,--store	   name of the store to operate on\n");
135	printf("  -N,-n,--noreload do not reload policy after commit\n");
136	printf("  -h,--help        print this message and quit\n");
137	printf("  -v,--verbose     be verbose\n");
138	printf("  -D,--disable_dontaudit	Remove dontaudits from policy\n");
139	printf("  -P,--preserve_tunables	Preserve tunables in policy\n");
140	printf("  -C,--ignore-module-cache	Rebuild CIL modules compiled from HLL files\n");
141	printf("  -p,--path        use an alternate path for the policy root\n");
142	printf("  -S,--store-path  use an alternate path for the policy store root\n");
143}
144
145/* Sets the global mode variable to new_mode, but only if no other
146 * mode has been given. */
147static void set_mode(enum client_modes new_mode, char *arg)
148{
149	struct command *c;
150	char *s;
151	if ((c = realloc(commands, sizeof(*c) * (num_commands + 1))) == NULL) {
152		fprintf(stderr, "Out of memory!\n");
153		cleanup();
154		exit(1);
155	}
156	commands = c;
157	commands[num_commands].mode = new_mode;
158	commands[num_commands].arg = NULL;
159	num_commands++;
160	if (arg != NULL) {
161		if ((s = strdup(arg)) == NULL) {
162			fprintf(stderr, "Out of memory!\n");
163			cleanup();
164			exit(1);
165		}
166		commands[num_commands - 1].arg = s;
167	}
168}
169
170/* Parse command line and set global options. */
171static void parse_command_line(int argc, char **argv)
172{
173	static struct option opts[] = {
174		{"store", required_argument, NULL, 's'},
175		{"base", required_argument, NULL, 'b'},
176		{"help", 0, NULL, 'h'},
177		{"install", required_argument, NULL, 'i'},
178		{"list-modules", optional_argument, NULL, 'l'},
179		{"verbose", 0, NULL, 'v'},
180		{"remove", required_argument, NULL, 'r'},
181		{"upgrade", required_argument, NULL, 'u'},
182		{"reload", 0, NULL, 'R'},
183		{"noreload", 0, NULL, 'n'},
184		{"build", 0, NULL, 'B'},
185		{"disable_dontaudit", 0, NULL, 'D'},
186		{"preserve_tunables", 0, NULL, 'P'},
187		{"ignore-module-cache", 0, NULL, 'C'},
188		{"priority", required_argument, NULL, 'X'},
189		{"enable", required_argument, NULL, 'e'},
190		{"disable", required_argument, NULL, 'd'},
191		{"path", required_argument, NULL, 'p'},
192		{"store-path", required_argument, NULL, 'S'},
193		{NULL, 0, NULL, 0}
194	};
195	int i;
196	verbose = 0;
197	reload = 0;
198	no_reload = 0;
199	priority = 400;
200	while ((i =
201		getopt_long(argc, argv, "s:b:hi:l::vqr:u:RnNBDCPX:e:d:p:S:", opts,
202			    NULL)) != -1) {
203		switch (i) {
204		case 'b':
205			fprintf(stderr, "The --base option is deprecated. Use --install instead.\n");
206			set_mode(INSTALL_M, optarg);
207			break;
208		case 'h':
209			usage(argv[0]);
210			exit(0);
211		case 'i':
212			set_mode(INSTALL_M, optarg);
213			break;
214		case 'l':
215			set_mode(LIST_M, optarg);
216			break;
217		case 'v':
218			verbose = 1;
219			break;
220		case 'r':
221			set_mode(REMOVE_M, optarg);
222			break;
223		case 'u':
224			fprintf(stderr, "The --upgrade option is deprecated. Use --install instead.\n");
225			set_mode(INSTALL_M, optarg);
226			break;
227		case 's':
228			set_store(optarg);
229			break;
230		case 'p':
231			semanage_set_root(optarg);
232			break;
233		case 'S':
234			set_store_root(optarg);
235			break;
236		case 'R':
237			reload = 1;
238			break;
239		case 'n':
240			no_reload = 1;
241			break;
242		case 'N':
243			no_reload = 1;
244			break;
245		case 'B':
246			build = 1;
247			break;
248		case 'D':
249			disable_dontaudit = 1;
250			break;
251		case 'P':
252			preserve_tunables = 1;
253			break;
254		case 'C':
255			ignore_module_cache = 1;
256			break;
257		case 'X':
258			set_mode(PRIORITY_M, optarg);
259			break;
260		case 'e':
261			set_mode(ENABLE_M, optarg);
262			break;
263		case 'd':
264			set_mode(DISABLE_M, optarg);
265			break;
266		case '?':
267		default:{
268				usage(argv[0]);
269				exit(1);
270			}
271		}
272	}
273	if ((build || reload) && num_commands) {
274		fprintf(stderr,
275			"build or reload should not be used with other commands\n");
276		usage(argv[0]);
277		exit(1);
278	}
279	if (num_commands == 0 && reload == 0 && build == 0) {
280		fprintf(stderr, "At least one mode must be specified.\n");
281		usage(argv[0]);
282		exit(1);
283	}
284
285	if (optind < argc) {
286		int mode;
287		/* if -i/u/r was the last command treat any remaining
288		 * arguments as args. Will allow 'semodule -i *.pp' to
289		 * work as expected.
290		 */
291
292		if (commands && commands[num_commands - 1].mode == INSTALL_M) {
293			mode = INSTALL_M;
294		} else if (commands && commands[num_commands - 1].mode == REMOVE_M) {
295			mode = REMOVE_M;
296		} else {
297			fprintf(stderr, "unknown additional arguments:\n");
298			while (optind < argc)
299				fprintf(stderr, " %s", argv[optind++]);
300			fprintf(stderr, "\n\n");
301			usage(argv[0]);
302			exit(1);
303		}
304		while (optind < argc)
305			set_mode(mode, argv[optind++]);
306	}
307}
308
309int main(int argc, char *argv[])
310{
311	int i, commit = 0;
312	int result;
313	int status = EXIT_FAILURE;
314	char *genhomedirconargv[] = { "genhomedircon", "-B", "-n" };
315	create_signal_handlers();
316	if (strcmp(basename(argv[0]), "genhomedircon") == 0) {
317		argc = 3;
318		argv=genhomedirconargv;
319	}
320	parse_command_line(argc, argv);
321
322	if (build)
323		commit = 1;
324
325	sh = semanage_handle_create();
326	if (!sh) {
327		fprintf(stderr, "%s:  Could not create semanage handle\n",
328			argv[0]);
329		goto cleanup_nohandle;
330	}
331
332	if (store) {
333		/* Set the store we want to connect to, before connecting.
334		 * this will always set a direct connection now, an additional
335		 * option will need to be used later to specify a policy server
336		 * location */
337		semanage_select_store(sh, store, SEMANAGE_CON_DIRECT);
338	}
339
340	if (store_root) {
341		semanage_set_store_root(sh, store_root);
342	}
343
344	/* create store if necessary, for bootstrapping */
345	semanage_set_create_store(sh, 1);
346
347	if ((result = semanage_connect(sh)) < 0) {
348		fprintf(stderr, "%s:  Could not connect to policy handler\n",
349			argv[0]);
350		goto cleanup;
351	}
352
353	if (reload) {
354		if ((result = semanage_reload_policy(sh)) < 0) {
355			fprintf(stderr, "%s:  Could not reload policy\n",
356				argv[0]);
357			goto cleanup;
358		}
359	}
360
361	if (build) {
362		if ((result = semanage_begin_transaction(sh)) < 0) {
363			fprintf(stderr, "%s:  Could not begin transaction:  %s\n",
364				argv[0], errno ? strerror(errno) : "");
365			goto cleanup;
366		}
367	}
368
369	if ((result = semanage_set_default_priority(sh, priority)) != 0) {
370		fprintf(stderr,
371			"%s: Invalid priority %d (needs to be between 1 and 999)\n",
372			argv[0],
373			priority);
374		goto cleanup;
375	}
376
377	for (i = 0; i < num_commands; i++) {
378		enum client_modes mode = commands[i].mode;
379		char *mode_arg = commands[i].arg;
380
381		switch (mode) {
382		case INSTALL_M:{
383				if (verbose) {
384					printf
385					    ("Attempting to install module '%s':\n",
386					     mode_arg);
387				}
388				result =
389				    semanage_module_install_file(sh, mode_arg);
390				break;
391			}
392		case REMOVE_M:{
393				if (verbose) {
394					printf
395					    ("Attempting to remove module '%s':\n",
396					     mode_arg);
397				}
398				result = semanage_module_remove(sh, mode_arg);
399				if ( result == -2 ) {
400					continue;
401				}
402				break;
403			}
404		case LIST_M:{
405				semanage_module_info_t *modinfos = NULL;
406				int modinfos_len = 0;
407				semanage_module_info_t *m = NULL;
408				int j = 0;
409
410				if (verbose) {
411					printf
412					    ("Attempting to list active modules:\n");
413				}
414
415				if (mode_arg == NULL || strcmp(mode_arg, "standard") == 0) {
416					result = semanage_module_list(sh,
417								      &modinfos,
418								      &modinfos_len);
419					if (result < 0) goto cleanup_list;
420
421					if (modinfos_len == 0) {
422						printf("No modules.\n");
423					}
424
425					const char *name = NULL;
426
427					for (j = 0; j < modinfos_len; j++) {
428						m = semanage_module_list_nth(modinfos, j);
429
430						result = semanage_module_info_get_name(sh, m, &name);
431						if (result != 0) goto cleanup_list;
432
433						printf("%s\n", name);
434					}
435				}
436				else if (strcmp(mode_arg, "full") == 0) {
437					/* get the modules */
438					result = semanage_module_list_all(sh,
439									  &modinfos,
440									  &modinfos_len);
441					if (result != 0) goto cleanup_list;
442
443					if (modinfos_len == 0) {
444						printf("No modules.\n");
445					}
446
447					/* calculate column widths */
448					size_t column[4] = { 0, 0, 0, 0 };
449
450					/* fixed width columns */
451					column[0] = sizeof("000") - 1;
452					column[3] = sizeof("disabled") - 1;
453
454					/* variable width columns */
455					const char *tmp = NULL;
456					size_t size;
457					for (j = 0; j < modinfos_len; j++) {
458						m = semanage_module_list_nth(modinfos, j);
459
460						result = semanage_module_info_get_name(sh, m, &tmp);
461						if (result != 0) goto cleanup_list;
462
463						size = strlen(tmp);
464						if (size > column[1]) column[1] = size;
465
466						result = semanage_module_info_get_lang_ext(sh, m, &tmp);
467						if (result != 0) goto cleanup_list;
468
469						size = strlen(tmp);
470						if (size > column[3]) column[3] = size;
471					}
472
473					/* print out each module */
474					for (j = 0; j < modinfos_len; j++) {
475						uint16_t pri = 0;
476						const char *name = NULL;
477						int enabled = 0;
478						const char *lang_ext = NULL;
479
480						m = semanage_module_list_nth(modinfos, j);
481
482						result = semanage_module_info_get_priority(sh, m, &pri);
483						if (result != 0) goto cleanup_list;
484
485						result = semanage_module_info_get_name(sh, m, &name);
486						if (result != 0) goto cleanup_list;
487
488						result = semanage_module_info_get_enabled(sh, m, &enabled);
489						if (result != 0) goto cleanup_list;
490
491						result = semanage_module_info_get_lang_ext(sh, m, &lang_ext);
492						if (result != 0) goto cleanup_list;
493
494						printf("%0*u %-*s %-*s %-*s\n",
495							(int)column[0], pri,
496							(int)column[1], name,
497							(int)column[2], lang_ext,
498							(int)column[3], enabled ? "" : "disabled");
499					}
500				}
501				else {
502					result = -1;
503				}
504
505cleanup_list:
506				for (j = 0; j < modinfos_len; j++) {
507					m = semanage_module_list_nth(modinfos, j);
508					semanage_module_info_destroy(sh, m);
509				}
510
511				free(modinfos);
512
513				break;
514			}
515		case PRIORITY_M:{
516				char *endptr = NULL;
517				priority = (uint16_t)strtoul(mode_arg, &endptr, 10);
518
519				if ((result = semanage_set_default_priority(sh, priority)) != 0) {
520					fprintf(stderr,
521						"%s: Invalid priority %d (needs to be between 1 and 999)\n",
522						argv[0],
523						priority);
524					goto cleanup;
525				}
526
527				break;
528			}
529		case ENABLE_M:{
530				if (verbose) {
531					printf
532					    ("Attempting to enable module '%s':\n",
533					     mode_arg);
534				}
535
536				semanage_module_key_t *modkey = NULL;
537
538				result = semanage_module_key_create(sh, &modkey);
539				if (result != 0) goto cleanup_enable;
540
541				result = semanage_module_key_set_name(sh, modkey, mode_arg);
542				if (result != 0) goto cleanup_enable;
543
544				result = semanage_module_set_enabled(sh, modkey, 1);
545				if (result != 0) goto cleanup_enable;
546
547cleanup_enable:
548				semanage_module_key_destroy(sh, modkey);
549				free(modkey);
550
551				break;
552			}
553		case DISABLE_M:{
554				if (verbose) {
555					printf
556					    ("Attempting to disable module '%s':\n",
557					     mode_arg);
558				}
559
560				semanage_module_key_t *modkey = NULL;
561
562				result = semanage_module_key_create(sh, &modkey);
563				if (result != 0) goto cleanup_disable;
564
565				result = semanage_module_key_set_name(sh, modkey, mode_arg);
566				if (result != 0) goto cleanup_disable;
567
568				result = semanage_module_set_enabled(sh, modkey, 0);
569				if (result != 0) goto cleanup_disable;
570
571cleanup_disable:
572				semanage_module_key_destroy(sh, modkey);
573				free(modkey);
574
575				break;
576			}
577		default:{
578				fprintf(stderr,
579					"%s:  Unknown mode specified.\n",
580					argv[0]);
581				usage(argv[0]);
582				goto cleanup;
583			}
584		}
585		commit += do_commit[mode];
586		if (result < 0) {
587			fprintf(stderr, "%s:  Failed on %s!\n", argv[0],
588				mode_arg ? : "list");
589			goto cleanup;
590		} else if (verbose) {
591			printf("Ok: return value of %d.\n", result);
592		}
593	}
594
595	if (commit) {
596		if (verbose)
597			printf("Committing changes:\n");
598		if (no_reload)
599			semanage_set_reload(sh, 0);
600		if (build)
601			semanage_set_rebuild(sh, 1);
602		if (disable_dontaudit)
603			semanage_set_disable_dontaudit(sh, 1);
604		else if (build)
605			semanage_set_disable_dontaudit(sh, 0);
606		if (preserve_tunables)
607			semanage_set_preserve_tunables(sh, 1);
608		if (ignore_module_cache)
609			semanage_set_ignore_module_cache(sh, 1);
610
611		result = semanage_commit(sh);
612	}
613
614	if (result < 0) {
615		fprintf(stderr, "%s:  Failed!\n", argv[0]);
616		goto cleanup;
617	} else if (commit && verbose) {
618		printf("Ok: transaction number %d.\n", result);
619	}
620
621	if (semanage_disconnect(sh) < 0) {
622		fprintf(stderr, "%s:  Error disconnecting\n", argv[0]);
623		goto cleanup;
624	}
625	status = EXIT_SUCCESS;
626
627      cleanup:
628	if (semanage_is_connected(sh)) {
629		if (semanage_disconnect(sh) < 0) {
630			fprintf(stderr, "%s:  Error disconnecting\n", argv[0]);
631		}
632	}
633	semanage_handle_destroy(sh);
634
635      cleanup_nohandle:
636	cleanup();
637	exit(status);
638}
639