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#include <limits.h>
24
25#include <semanage/modules.h>
26
27enum client_modes {
28	NO_MODE, INSTALL_M, REMOVE_M, EXTRACT_M, CIL_M, HLL_M,
29	LIST_M, RELOAD, PRIORITY_M, ENABLE_M, DISABLE_M
30};
31/* list of modes in which one ought to commit afterwards */
32static const int do_commit[] = {
33	0, 1, 1, 0, 0, 0,
34	0, 0, 0, 1, 1,
35};
36
37struct command {
38	enum client_modes mode;
39	char *arg;
40};
41static struct command *commands = NULL;
42static int num_commands = 0;
43
44/* options given on command line */
45static int verbose;
46static int reload;
47static int no_reload;
48static int build;
49static int disable_dontaudit;
50static int preserve_tunables;
51static int ignore_module_cache;
52static uint16_t priority;
53static int priority_set = 0;
54
55static semanage_handle_t *sh = NULL;
56static char *store;
57static char *store_root;
58int extract_cil = 0;
59
60extern char *optarg;
61extern int optind;
62
63static void cleanup(void)
64{
65	while (--num_commands >= 0) {
66		free(commands[num_commands].arg);
67	}
68	free(commands);
69}
70
71/* Signal handlers. */
72static void handle_signal(int sig_num)
73{
74	if (sig_num == SIGINT || sig_num == SIGQUIT || sig_num == SIGTERM) {
75		/* catch these signals, and then drop them */
76	}
77}
78
79static void set_store(char *storename)
80{
81	/* For now this only supports a store name, later on this
82	 * should support an address for a remote connection */
83
84	if ((store = strdup(storename)) == NULL) {
85		fprintf(stderr, "Out of memory!\n");
86		goto bad;
87	}
88
89	return;
90
91      bad:
92	cleanup();
93	exit(1);
94}
95
96static void set_store_root(char *path)
97{
98	if ((store_root = strdup(path)) == NULL) {
99		fprintf(stderr, "Out of memory!\n");
100		goto bad;
101	}
102
103	return;
104
105      bad:
106	cleanup();
107	exit(1);
108}
109
110/* Establish signal handlers for the process. */
111static void create_signal_handlers(void)
112{
113	if (signal(SIGINT, handle_signal) == SIG_ERR ||
114	    signal(SIGQUIT, handle_signal) == SIG_ERR ||
115	    signal(SIGTERM, handle_signal) == SIG_ERR) {
116		fprintf(stderr, "Could not set up signal handler.\n");
117		exit(255);
118	}
119}
120
121static void usage(char *progname)
122{
123	printf("usage:  %s [options]... MODE [MODES]...\n", progname);
124	printf("Manage SELinux policy modules.\n");
125	printf("MODES:\n");
126	printf("  -R, --reload		    reload policy\n");
127	printf("  -B, --build		    build and reload policy\n");
128	printf("  -i,--install=MODULE_PKG   install a new module\n");
129	printf("  -r,--remove=MODULE_NAME   remove existing module\n");
130	printf("  -l,--list-modules=[KIND]  display list of installed modules\n");
131	printf("     KIND:  standard  list highest priority, enabled modules\n");
132	printf("            full      list all modules\n");
133	printf("  -X,--priority=PRIORITY    set priority for following operations (1-999)\n");
134	printf("  -e,--enable=MODULE_NAME   enable module\n");
135	printf("  -d,--disable=MODULE_NAME  disable module\n");
136	printf("  -E,--extract=MODULE_NAME  extract module\n");
137	printf("Other options:\n");
138	printf("  -s,--store	   name of the store to operate on\n");
139	printf("  -N,-n,--noreload do not reload policy after commit\n");
140	printf("  -h,--help        print this message and quit\n");
141	printf("  -v,--verbose     be verbose\n");
142	printf("  -D,--disable_dontaudit	Remove dontaudits from policy\n");
143	printf("  -P,--preserve_tunables	Preserve tunables in policy\n");
144	printf("  -C,--ignore-module-cache	Rebuild CIL modules compiled from HLL files\n");
145	printf("  -p,--path        use an alternate path for the policy root\n");
146	printf("  -S,--store-path  use an alternate path for the policy store root\n");
147	printf("  -c, --cil extract module as cil. This only affects module extraction.\n");
148	printf("  -H, --hll extract module as hll. This only affects module extraction.\n");
149}
150
151/* Sets the global mode variable to new_mode, but only if no other
152 * mode has been given. */
153static void set_mode(enum client_modes new_mode, char *arg)
154{
155	struct command *c;
156	char *s;
157	if ((c = realloc(commands, sizeof(*c) * (num_commands + 1))) == NULL) {
158		fprintf(stderr, "Out of memory!\n");
159		cleanup();
160		exit(1);
161	}
162	commands = c;
163	commands[num_commands].mode = new_mode;
164	commands[num_commands].arg = NULL;
165	num_commands++;
166	if (arg != NULL) {
167		if ((s = strdup(arg)) == NULL) {
168			fprintf(stderr, "Out of memory!\n");
169			cleanup();
170			exit(1);
171		}
172		commands[num_commands - 1].arg = s;
173	}
174}
175
176/* Parse command line and set global options. */
177static void parse_command_line(int argc, char **argv)
178{
179	static struct option opts[] = {
180		{"store", required_argument, NULL, 's'},
181		{"base", required_argument, NULL, 'b'},
182		{"help", 0, NULL, 'h'},
183		{"install", required_argument, NULL, 'i'},
184		{"extract", required_argument, NULL, 'E'},
185		{"cil", 0, NULL, 'c'},
186		{"hll", 0, NULL, 'H'},
187		{"list-modules", optional_argument, NULL, 'l'},
188		{"verbose", 0, NULL, 'v'},
189		{"remove", required_argument, NULL, 'r'},
190		{"upgrade", required_argument, NULL, 'u'},
191		{"reload", 0, NULL, 'R'},
192		{"noreload", 0, NULL, 'n'},
193		{"build", 0, NULL, 'B'},
194		{"disable_dontaudit", 0, NULL, 'D'},
195		{"preserve_tunables", 0, NULL, 'P'},
196		{"ignore-module-cache", 0, NULL, 'C'},
197		{"priority", required_argument, NULL, 'X'},
198		{"enable", required_argument, NULL, 'e'},
199		{"disable", required_argument, NULL, 'd'},
200		{"path", required_argument, NULL, 'p'},
201		{"store-path", required_argument, NULL, 'S'},
202		{NULL, 0, NULL, 0}
203	};
204	int extract_selected = 0;
205	int cil_hll_set = 0;
206	int i;
207	verbose = 0;
208	reload = 0;
209	no_reload = 0;
210	priority = 400;
211	while ((i =
212		getopt_long(argc, argv, "s:b:hi:l::vqr:u:RnNBDCPX:e:d:p:S:E:cH", opts,
213			    NULL)) != -1) {
214		switch (i) {
215		case 'b':
216			fprintf(stderr, "The --base option is deprecated. Use --install instead.\n");
217			set_mode(INSTALL_M, optarg);
218			break;
219		case 'h':
220			usage(argv[0]);
221			exit(0);
222		case 'i':
223			set_mode(INSTALL_M, optarg);
224			break;
225		case 'E':
226			set_mode(EXTRACT_M, optarg);
227			extract_selected = 1;
228			break;
229		case 'c':
230			set_mode(CIL_M, NULL);
231			cil_hll_set = 1;
232			break;
233		case 'H':
234			set_mode(HLL_M, NULL);
235			cil_hll_set = 1;
236			break;
237		case 'l':
238			set_mode(LIST_M, optarg);
239			break;
240		case 'v':
241			verbose = 1;
242			break;
243		case 'r':
244			set_mode(REMOVE_M, optarg);
245			break;
246		case 'u':
247			fprintf(stderr, "The --upgrade option is deprecated. Use --install instead.\n");
248			set_mode(INSTALL_M, optarg);
249			break;
250		case 's':
251			set_store(optarg);
252			break;
253		case 'p':
254			semanage_set_root(optarg);
255			break;
256		case 'S':
257			set_store_root(optarg);
258			break;
259		case 'R':
260			reload = 1;
261			break;
262		case 'n':
263			no_reload = 1;
264			break;
265		case 'N':
266			no_reload = 1;
267			break;
268		case 'B':
269			build = 1;
270			break;
271		case 'D':
272			disable_dontaudit = 1;
273			break;
274		case 'P':
275			preserve_tunables = 1;
276			break;
277		case 'C':
278			ignore_module_cache = 1;
279			break;
280		case 'X':
281			set_mode(PRIORITY_M, optarg);
282			break;
283		case 'e':
284			set_mode(ENABLE_M, optarg);
285			break;
286		case 'd':
287			set_mode(DISABLE_M, optarg);
288			break;
289		case '?':
290		default:{
291				usage(argv[0]);
292				exit(1);
293			}
294		}
295	}
296	if ((build || reload) && num_commands) {
297		fprintf(stderr,
298			"build or reload should not be used with other commands\n");
299		usage(argv[0]);
300		exit(1);
301	}
302	if (num_commands == 0 && reload == 0 && build == 0) {
303		fprintf(stderr, "At least one mode must be specified.\n");
304		usage(argv[0]);
305		exit(1);
306	}
307	if (extract_selected == 0 && cil_hll_set == 1) {
308		fprintf(stderr, "--cil and --hll require a module to export with the --extract option.\n");
309		usage(argv[0]);
310		exit(1);
311	}
312
313	if (optind < argc) {
314		int mode;
315		/* if -i/u/r/E was the last command treat any remaining
316		 * arguments as args. Will allow 'semodule -i *.pp' to
317		 * work as expected.
318		 */
319
320		if (commands && commands[num_commands - 1].mode == INSTALL_M) {
321			mode = INSTALL_M;
322		} else if (commands && commands[num_commands - 1].mode == REMOVE_M) {
323			mode = REMOVE_M;
324		} else if (commands && commands[num_commands - 1].mode == EXTRACT_M) {
325			mode = EXTRACT_M;
326		} else {
327			fprintf(stderr, "unknown additional arguments:\n");
328			while (optind < argc)
329				fprintf(stderr, " %s", argv[optind++]);
330			fprintf(stderr, "\n\n");
331			usage(argv[0]);
332			exit(1);
333		}
334		while (optind < argc)
335			set_mode(mode, argv[optind++]);
336	}
337}
338
339int main(int argc, char *argv[])
340{
341	int i, commit = 0;
342	int result;
343	int status = EXIT_FAILURE;
344	char *genhomedirconargv[] = { "genhomedircon", "-B", "-n" };
345	create_signal_handlers();
346	if (strcmp(basename(argv[0]), "genhomedircon") == 0) {
347		argc = 3;
348		argv=genhomedirconargv;
349	}
350	parse_command_line(argc, argv);
351
352	if (build)
353		commit = 1;
354
355	sh = semanage_handle_create();
356	if (!sh) {
357		fprintf(stderr, "%s:  Could not create semanage handle\n",
358			argv[0]);
359		goto cleanup_nohandle;
360	}
361
362	if (store) {
363		/* Set the store we want to connect to, before connecting.
364		 * this will always set a direct connection now, an additional
365		 * option will need to be used later to specify a policy server
366		 * location */
367		semanage_select_store(sh, store, SEMANAGE_CON_DIRECT);
368	}
369
370	if (store_root) {
371		semanage_set_store_root(sh, store_root);
372	}
373
374	/* create store if necessary, for bootstrapping */
375	semanage_set_create_store(sh, 1);
376
377	if ((result = semanage_connect(sh)) < 0) {
378		fprintf(stderr, "%s:  Could not connect to policy handler\n",
379			argv[0]);
380		goto cleanup;
381	}
382
383	if (reload) {
384		if ((result = semanage_reload_policy(sh)) < 0) {
385			fprintf(stderr, "%s:  Could not reload policy\n",
386				argv[0]);
387			goto cleanup;
388		}
389	}
390
391	if (build) {
392		if ((result = semanage_begin_transaction(sh)) < 0) {
393			fprintf(stderr, "%s:  Could not begin transaction:  %s\n",
394				argv[0], errno ? strerror(errno) : "");
395			goto cleanup;
396		}
397	}
398
399	if ((result = semanage_set_default_priority(sh, priority)) != 0) {
400		fprintf(stderr,
401			"%s: Invalid priority %d (needs to be between 1 and 999)\n",
402			argv[0],
403			priority);
404		goto cleanup;
405	}
406
407	for (i = 0; i < num_commands; i++) {
408		enum client_modes mode = commands[i].mode;
409		char *mode_arg = commands[i].arg;
410
411		switch (mode) {
412		case INSTALL_M:{
413				if (verbose) {
414					printf
415					    ("Attempting to install module '%s':\n",
416					     mode_arg);
417				}
418				result =
419				    semanage_module_install_file(sh, mode_arg);
420				break;
421			}
422		case EXTRACT_M:{
423				semanage_module_info_t *extract_info = NULL;
424				semanage_module_key_t *modkey = NULL;
425				uint16_t curr_priority;
426				void *data = NULL;
427				size_t data_len = 0;
428				char output_path[PATH_MAX];
429				const char *output_name = NULL;
430				const char *lang_ext = NULL;
431				int rlen;
432				FILE *output_fd = NULL;
433
434				result = semanage_module_key_create(sh, &modkey);
435				if (result != 0) {
436					goto cleanup_extract;
437				}
438
439				result = semanage_module_key_set_name(sh, modkey, mode_arg);
440				if (result != 0) {
441					goto cleanup_extract;
442				}
443
444				if (priority_set == 0) {
445					result = semanage_module_get_module_info(sh, modkey, &extract_info);
446					if (result != 0) {
447						goto cleanup_extract;
448					}
449
450					semanage_module_info_get_priority(sh, extract_info, &curr_priority);
451					printf("Module '%s' does not exist at the default priority '%d'. "
452							"Extracting at highest existing priority '%d'.\n", mode_arg, priority, curr_priority);
453					priority = curr_priority;
454				}
455
456				result  = semanage_module_key_set_priority(sh, modkey, priority);
457				if (result != 0) {
458					goto cleanup_extract;
459				}
460
461				if (verbose) {
462					printf
463						("Attempting to extract module '%s':\n",
464							mode_arg);
465				}
466				result = semanage_module_extract(sh, modkey, extract_cil, &data, &data_len, &extract_info);
467				if (result != 0) {
468					goto cleanup_extract;
469				}
470
471				if (extract_cil) {
472					lang_ext = "cil";
473				} else {
474					result = semanage_module_info_get_lang_ext(sh, extract_info, &lang_ext);
475					if (result != 0) {
476						goto cleanup_extract;
477					}
478				}
479
480				result = semanage_module_info_get_name(sh, extract_info, &output_name);
481				if (result != 0) {
482					goto cleanup_extract;
483				}
484
485				rlen = snprintf(output_path, PATH_MAX, "%s.%s", output_name, lang_ext);
486				if (rlen < 0 || rlen >= PATH_MAX) {
487					fprintf(stderr, "%s: Failed to generate output path.\n", argv[0]);
488					result = -1;
489					goto cleanup_extract;
490				}
491
492				if (access(output_path, F_OK) == 0) {
493					fprintf(stderr, "%s: %s is already extracted with extension %s.\n", argv[0], mode_arg, lang_ext);
494					result = -1;
495					goto cleanup_extract;
496				}
497
498				output_fd = fopen(output_path, "w");
499				if (output_fd == NULL) {
500					fprintf(stderr, "%s: Unable to open %s\n", argv[0], output_path);
501					result = -1;
502					goto cleanup_extract;
503				}
504
505				if (fwrite(data, 1, data_len, output_fd) < data_len) {
506					fprintf(stderr, "%s: Unable to write to %s\n", argv[0], output_path);
507					result = -1;
508					goto cleanup_extract;
509				}
510cleanup_extract:
511				if (output_fd != NULL) {
512					fclose(output_fd);
513				}
514				if (data_len > 0) {
515					munmap(data, data_len);
516				}
517				semanage_module_info_destroy(sh, extract_info);
518				free(extract_info);
519				semanage_module_key_destroy(sh, modkey);
520				free(modkey);
521				break;
522			}
523		case CIL_M:
524				extract_cil = 1;
525				break;
526		case HLL_M:
527				extract_cil = 0;
528				break;
529		case REMOVE_M:{
530				if (verbose) {
531					printf
532					    ("Attempting to remove module '%s':\n",
533					     mode_arg);
534				}
535				result = semanage_module_remove(sh, mode_arg);
536				if ( result == -2 ) {
537					continue;
538				}
539				break;
540			}
541		case LIST_M:{
542				semanage_module_info_t *modinfos = NULL;
543				int modinfos_len = 0;
544				semanage_module_info_t *m = NULL;
545				int j = 0;
546
547				if (verbose) {
548					printf
549					    ("Attempting to list active modules:\n");
550				}
551
552				if (mode_arg == NULL || strcmp(mode_arg, "standard") == 0) {
553					result = semanage_module_list(sh,
554								      &modinfos,
555								      &modinfos_len);
556					if (result < 0) goto cleanup_list;
557
558					if (modinfos_len == 0) {
559						printf("No modules.\n");
560					}
561
562					const char *name = NULL;
563
564					for (j = 0; j < modinfos_len; j++) {
565						m = semanage_module_list_nth(modinfos, j);
566
567						result = semanage_module_info_get_name(sh, m, &name);
568						if (result != 0) goto cleanup_list;
569
570						printf("%s\n", name);
571					}
572				}
573				else if (strcmp(mode_arg, "full") == 0) {
574					/* get the modules */
575					result = semanage_module_list_all(sh,
576									  &modinfos,
577									  &modinfos_len);
578					if (result != 0) goto cleanup_list;
579
580					if (modinfos_len == 0) {
581						printf("No modules.\n");
582					}
583
584					/* calculate column widths */
585					size_t column[4] = { 0, 0, 0, 0 };
586
587					/* fixed width columns */
588					column[0] = sizeof("000") - 1;
589					column[3] = sizeof("disabled") - 1;
590
591					/* variable width columns */
592					const char *tmp = NULL;
593					size_t size;
594					for (j = 0; j < modinfos_len; j++) {
595						m = semanage_module_list_nth(modinfos, j);
596
597						result = semanage_module_info_get_name(sh, m, &tmp);
598						if (result != 0) goto cleanup_list;
599
600						size = strlen(tmp);
601						if (size > column[1]) column[1] = size;
602
603						result = semanage_module_info_get_lang_ext(sh, m, &tmp);
604						if (result != 0) goto cleanup_list;
605
606						size = strlen(tmp);
607						if (size > column[3]) column[3] = size;
608					}
609
610					/* print out each module */
611					for (j = 0; j < modinfos_len; j++) {
612						uint16_t pri = 0;
613						const char *name = NULL;
614						int enabled = 0;
615						const char *lang_ext = NULL;
616
617						m = semanage_module_list_nth(modinfos, j);
618
619						result = semanage_module_info_get_priority(sh, m, &pri);
620						if (result != 0) goto cleanup_list;
621
622						result = semanage_module_info_get_name(sh, m, &name);
623						if (result != 0) goto cleanup_list;
624
625						result = semanage_module_info_get_enabled(sh, m, &enabled);
626						if (result != 0) goto cleanup_list;
627
628						result = semanage_module_info_get_lang_ext(sh, m, &lang_ext);
629						if (result != 0) goto cleanup_list;
630
631						printf("%0*u %-*s %-*s %-*s\n",
632							(int)column[0], pri,
633							(int)column[1], name,
634							(int)column[2], lang_ext,
635							(int)column[3], enabled ? "" : "disabled");
636					}
637				}
638				else {
639					result = -1;
640				}
641
642cleanup_list:
643				for (j = 0; j < modinfos_len; j++) {
644					m = semanage_module_list_nth(modinfos, j);
645					semanage_module_info_destroy(sh, m);
646				}
647
648				free(modinfos);
649
650				break;
651			}
652		case PRIORITY_M:{
653				char *endptr = NULL;
654				priority = (uint16_t)strtoul(mode_arg, &endptr, 10);
655				priority_set = 1;
656
657				if ((result = semanage_set_default_priority(sh, priority)) != 0) {
658					fprintf(stderr,
659						"%s: Invalid priority %d (needs to be between 1 and 999)\n",
660						argv[0],
661						priority);
662					goto cleanup;
663				}
664
665				break;
666			}
667		case ENABLE_M:{
668				if (verbose) {
669					printf
670					    ("Attempting to enable module '%s':\n",
671					     mode_arg);
672				}
673
674				semanage_module_key_t *modkey = NULL;
675
676				result = semanage_module_key_create(sh, &modkey);
677				if (result != 0) goto cleanup_enable;
678
679				result = semanage_module_key_set_name(sh, modkey, mode_arg);
680				if (result != 0) goto cleanup_enable;
681
682				result = semanage_module_set_enabled(sh, modkey, 1);
683				if (result != 0) goto cleanup_enable;
684
685cleanup_enable:
686				semanage_module_key_destroy(sh, modkey);
687				free(modkey);
688
689				break;
690			}
691		case DISABLE_M:{
692				if (verbose) {
693					printf
694					    ("Attempting to disable module '%s':\n",
695					     mode_arg);
696				}
697
698				semanage_module_key_t *modkey = NULL;
699
700				result = semanage_module_key_create(sh, &modkey);
701				if (result != 0) goto cleanup_disable;
702
703				result = semanage_module_key_set_name(sh, modkey, mode_arg);
704				if (result != 0) goto cleanup_disable;
705
706				result = semanage_module_set_enabled(sh, modkey, 0);
707				if (result != 0) goto cleanup_disable;
708
709cleanup_disable:
710				semanage_module_key_destroy(sh, modkey);
711				free(modkey);
712
713				break;
714			}
715		default:{
716				fprintf(stderr,
717					"%s:  Unknown mode specified.\n",
718					argv[0]);
719				usage(argv[0]);
720				goto cleanup;
721			}
722		}
723		commit += do_commit[mode];
724		if (result < 0) {
725			fprintf(stderr, "%s:  Failed on %s!\n", argv[0],
726				mode_arg ? : "list");
727			goto cleanup;
728		} else if (verbose) {
729			printf("Ok: return value of %d.\n", result);
730		}
731	}
732
733	if (commit) {
734		if (verbose)
735			printf("Committing changes:\n");
736		if (no_reload)
737			semanage_set_reload(sh, 0);
738		if (build)
739			semanage_set_rebuild(sh, 1);
740		if (disable_dontaudit)
741			semanage_set_disable_dontaudit(sh, 1);
742		else if (build)
743			semanage_set_disable_dontaudit(sh, 0);
744		if (preserve_tunables)
745			semanage_set_preserve_tunables(sh, 1);
746		if (ignore_module_cache)
747			semanage_set_ignore_module_cache(sh, 1);
748
749		result = semanage_commit(sh);
750	}
751
752	if (result < 0) {
753		fprintf(stderr, "%s:  Failed!\n", argv[0]);
754		goto cleanup;
755	} else if (commit && verbose) {
756		printf("Ok: transaction number %d.\n", result);
757	}
758
759	if (semanage_disconnect(sh) < 0) {
760		fprintf(stderr, "%s:  Error disconnecting\n", argv[0]);
761		goto cleanup;
762	}
763	status = EXIT_SUCCESS;
764
765      cleanup:
766	if (semanage_is_connected(sh)) {
767		if (semanage_disconnect(sh) < 0) {
768			fprintf(stderr, "%s:  Error disconnecting\n", argv[0]);
769		}
770	}
771	semanage_handle_destroy(sh);
772
773      cleanup_nohandle:
774	cleanup();
775	exit(status);
776}
777