/* Authors: Karl MacMillan <kmacmillan@tresys.com>
 *
 * Copyright (C) 2004 Tresys Technology, LLC
 *	This program is free software; you can redistribute it and/or modify
 *  	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, version 2.
 */

#include <sepol/module.h>
#include <getopt.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>

static void usage(const char *prog)
{
	printf("usage: %s -o <output file> -m <module> [-f <file contexts>]\n",
	       prog);
	printf("Options:\n");
	printf("  -o --outfile		Output file (required)\n");
	printf("  -m --module		Module file (required)\n");
	printf("  -f --fc		File contexts file\n");
	printf("  -s --seuser		Seusers file (only valid in base)\n");
	printf("  -u --user_extra	user_extra file (only valid in base)\n");
	printf("  -n --nc		Netfilter contexts file\n");
	printf("  -h --help		Show this help message\n");
}

static int file_to_data(const char *path, char **data, size_t *len,
			const char *progname)
{
	int fd;
	struct stat sb;
	fd = open(path, O_RDONLY | O_CLOEXEC);
	if (fd < 0) {
		fprintf(stderr, "%s:  Failed to open %s:  %s\n", progname, path,
			strerror(errno));
		return -1;
	}
	if (fstat(fd, &sb) < 0) {
		fprintf(stderr, "%s:  Failed to fstat %s:  %s\n", progname,
			path, strerror(errno));
		goto err;
	}
	if (!sb.st_size) {
		close(fd);
		*len = 0;
		return 0;
	}

	*data = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
	if (*data == MAP_FAILED) {
		fprintf(stderr, "%s:  Failed to mmap %s:  %s\n", progname, path,
			strerror(errno));
		goto err;
	}
	*len = sb.st_size;
	close(fd);
	return 0;
err:
	close(fd);
	return -1;
}

int main(int argc, char **argv)
{
	struct sepol_module_package *pkg = NULL;
	struct sepol_policy_file *mod = NULL, *out = NULL;
	FILE *fp = NULL;
	char *module = NULL, *file_contexts = NULL, *seusers = NULL,
	     *user_extra = NULL;
	char *fcdata = NULL, *outfile = NULL, *seusersdata = NULL,
	     *user_extradata = NULL;
	char *netfilter_contexts = NULL, *ncdata = NULL;
	size_t fclen = 0, seuserslen = 0, user_extralen = 0, nclen = 0;
	int i, ret;

	const struct option opts[] = {
		{ "module", required_argument, NULL, 'm' },
		{ "fc", required_argument, NULL, 'f' },
		{ "seuser", required_argument, NULL, 's' },
		{ "user_extra", required_argument, NULL, 'u' },
		{ "nc", required_argument, NULL, 'n' },
		{ "outfile", required_argument, NULL, 'o' },
		{ "help", 0, NULL, 'h' },
		{ NULL, 0, NULL, 0 }
	};

	while ((i = getopt_long(argc, argv, "m:f:s:u:o:n:h", opts, NULL)) !=
	       -1) {
		switch (i) {
		case 'h':
			usage(argv[0]);
			return EXIT_SUCCESS;
		case 'm':
			if (module) {
				fprintf(stderr,
					"May not specify more than one module\n");
				return EXIT_FAILURE;
			}
			module = strdup(optarg);
			if (!module) {
				fprintf(stderr, "%s:  Out of memory\n",
					argv[0]);
				return EXIT_FAILURE;
			}
			break;
		case 'f':
			if (file_contexts) {
				fprintf(stderr,
					"May not specify more than one file context file\n");
				return EXIT_FAILURE;
			}
			file_contexts = strdup(optarg);
			if (!file_contexts) {
				fprintf(stderr, "%s:  Out of memory\n",
					argv[0]);
				return EXIT_FAILURE;
			}
			break;
		case 'o':
			if (outfile) {
				fprintf(stderr,
					"May not specify more than one output file\n");
				return EXIT_FAILURE;
			}
			outfile = strdup(optarg);
			if (!outfile) {
				fprintf(stderr, "%s:  Out of memory\n",
					argv[0]);
				return EXIT_FAILURE;
			}
			break;
		case 's':
			if (seusers) {
				fprintf(stderr,
					"May not specify more than one seuser file\n");
				return EXIT_FAILURE;
			}
			seusers = strdup(optarg);
			if (!seusers) {
				fprintf(stderr, "%s:  Out of memory\n",
					argv[0]);
				return EXIT_FAILURE;
			}
			break;
		case 'u':
			if (user_extra) {
				fprintf(stderr,
					"May not specify more than one user_extra file\n");
				return EXIT_FAILURE;
			}
			user_extra = strdup(optarg);
			if (!user_extra) {
				fprintf(stderr, "%s:  Out of memory\n",
					argv[0]);
				return EXIT_FAILURE;
			}
			break;
		case 'n':
			if (netfilter_contexts) {
				fprintf(stderr,
					"May not specify more than one netfilter contexts file\n");
				return EXIT_FAILURE;
			}
			netfilter_contexts = strdup(optarg);
			if (!netfilter_contexts) {
				fprintf(stderr, "%s:  Out of memory\n",
					argv[0]);
				return EXIT_FAILURE;
			}
			break;
		default:
			usage(argv[0]);
			return EXIT_FAILURE;
		}
	}

	if (optind < argc) {
		fprintf(stderr,
			"%s:  Superfluous command line arguments: ", argv[0]);
		while (optind < argc)
			fprintf(stderr, "%s ", argv[optind++]);
		fprintf(stderr, "\n");
		usage(argv[0]);
		return EXIT_FAILURE;
	}

	if (!module || !outfile) {
		usage(argv[0]);
		return EXIT_FAILURE;
	}

	if (file_contexts &&
	    file_to_data(file_contexts, &fcdata, &fclen, argv[0]))
		goto failure;

	if (seusers &&
	    file_to_data(seusers, &seusersdata, &seuserslen, argv[0]))
		goto failure;

	if (user_extra &&
	    file_to_data(user_extra, &user_extradata, &user_extralen, argv[0]))
		goto failure;

	if (netfilter_contexts &&
	    file_to_data(netfilter_contexts, &ncdata, &nclen, argv[0]))
		goto failure;

	if (sepol_policy_file_create(&mod)) {
		fprintf(stderr, "%s:  Out of memory\n", argv[0]);
		goto failure;
	}

	fp = fopen(module, "re");
	if (!fp) {
		fprintf(stderr, "%s:  Could not open file %s:  %s\n", argv[0],
			module, strerror(errno));
		goto failure;
	}
	sepol_policy_file_set_fp(mod, fp);

	if (sepol_module_package_create(&pkg)) {
		fprintf(stderr, "%s:  Out of memory\n", argv[0]);
		goto failure;
	}

	if (sepol_policydb_read(sepol_module_package_get_policy(pkg), mod)) {
		fprintf(stderr,
			"%s:  Error while reading policy module from %s\n",
			argv[0], module);
		goto failure;
	}

	fclose(fp);
	fp = NULL;

	if (fclen &&
	    sepol_module_package_set_file_contexts(pkg, fcdata, fclen)) {
		fprintf(stderr, "%s:  Error while setting file contexts\n",
			argv[0]);
		goto failure;
	}

	if (seuserslen &&
	    sepol_module_package_set_seusers(pkg, seusersdata, seuserslen)) {
		fprintf(stderr, "%s:  Error while setting seusers\n", argv[0]);
		goto failure;
	}

	if (user_extra && sepol_module_package_set_user_extra(
				  pkg, user_extradata, user_extralen)) {
		fprintf(stderr, "%s:  Error while setting extra users\n",
			argv[0]);
		goto failure;
	}

	if (nclen &&
	    sepol_module_package_set_netfilter_contexts(pkg, ncdata, nclen)) {
		fprintf(stderr, "%s:  Error while setting netfilter contexts\n",
			argv[0]);
		goto failure;
	}

	if (sepol_policy_file_create(&out)) {
		fprintf(stderr, "%s:  Out of memory\n", argv[0]);
		goto failure;
	}

	fp = fopen(outfile, "we");
	if (!fp) {
		fprintf(stderr, "%s:  Could not open file %s:  %s\n", argv[0],
			outfile, strerror(errno));
		goto failure;
	}
	sepol_policy_file_set_fp(out, fp);

	if (sepol_module_package_write(pkg, out)) {
		fprintf(stderr,
			"%s:  Error while writing module package to %s\n",
			argv[0], argv[1]);
		goto failure;
	}

	ret = fclose(fp);
	fp = NULL;
	if (ret) {
		fprintf(stderr, "%s:  Could not close file %s:  %s\n", argv[0],
			outfile, strerror(errno));
		goto failure;
	}

	ret = EXIT_SUCCESS;
	goto cleanup;

failure:
	ret = EXIT_FAILURE;

cleanup:
	if (fp)
		fclose(fp);
	sepol_policy_file_free(out);
	if (nclen)
		munmap(ncdata, nclen);
	if (user_extradata)
		munmap(user_extradata, user_extralen);
	if (seuserslen)
		munmap(seusersdata, seuserslen);
	if (fclen)
		munmap(fcdata, fclen);
	sepol_module_package_free(pkg);
	sepol_policy_file_free(mod);
	free(netfilter_contexts);
	free(user_extra);
	free(seusers);
	free(outfile);
	free(file_contexts);
	free(module);

	return ret;
}
