/*
 * Copyright (c) 1999, 2000, 2001, 2002 Robert N. M. Watson
 * Copyright (c) 2002, 2003 Networks Associates Technology, Inc.
 * All rights reserved.
 *
 * This software was developed by Robert Watson for the TrustedBSD Project.
 *
 * This software was developed for the FreeBSD Project in part by Network
 * Associates Laboratories, the Security Research Division of Network
 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"),
 * as part of the DARPA CHATS research program.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD$
 */

#include <sys/types.h>
#include <sys/queue.h>
#include <sys/sysctl.h>

#include <dlfcn.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/mac.h>

static int	internal_initialized;

/*
 * Maintain a list of default label preparations for various object
 * types.  Each name will appear only once in the list.
 *
 * XXXMAC: Not thread-safe.
 */
static LIST_HEAD(, label_default) label_default_head;
struct label_default {
	char				*ld_name;
	char				*ld_labels;
	LIST_ENTRY(label_default)	 ld_entries;
};

static void
mac_destroy_labels(void)
{
	struct label_default *ld;

	while ((ld = LIST_FIRST(&label_default_head))) {
		free(ld->ld_name);
		free(ld->ld_labels);
		LIST_REMOVE(ld, ld_entries);
		free(ld);
	}
}

static void
mac_destroy_internal(void)
{

	mac_destroy_labels();

	internal_initialized = 0;
}

static int
mac_add_type(const char *name, const char *labels)
{
	struct label_default *ld, *ld_new;
	char *name_dup, *labels_dup;

	/*
	 * Speculatively allocate all the memory now to avoid allocating
	 * later when we will someday hold a mutex.
	 */
	name_dup = strdup(name);
	if (name_dup == NULL) {
		errno = ENOMEM;
		return (-1);
	}
	labels_dup = strdup(labels);
	if (labels_dup == NULL) {
		free(name_dup);
		errno = ENOMEM;
		return (-1);
	}
	ld_new = malloc(sizeof(*ld));
	if (ld_new == NULL) {
		free(name_dup);
		free(labels_dup);
		errno = ENOMEM;
		return (-1);
	}

	/*
	 * If the type is already present, replace the current entry
	 * rather than add a new instance.
	 */
	for (ld = LIST_FIRST(&label_default_head); ld != NULL;
	    ld = LIST_NEXT(ld, ld_entries)) {
		if (strcmp(name, ld->ld_name) == 0)
			break;
	}

	if (ld != NULL) {
		free(ld->ld_labels);
		ld->ld_labels = labels_dup;
		labels_dup = NULL;
	} else {
		ld = ld_new;
		ld->ld_name = name_dup;
		ld->ld_labels = labels_dup;

		ld_new = NULL;
		name_dup = NULL;
		labels_dup = NULL;

		LIST_INSERT_HEAD(&label_default_head, ld, ld_entries);
	}

	if (name_dup != NULL)
		free(name_dup);
	if (labels_dup != NULL)
		free(labels_dup);
	if (ld_new != NULL)
		free(ld_new);

	return (0);
}

static char *
next_token(char **string)
{
	char *token;

	token = strsep(string, " \t");
	while (token != NULL && *token == '\0')
		token = strsep(string, " \t");

	return (token);
}

static int
mac_init_internal(int ignore_errors)
{
	const char *filename;
	char line[LINE_MAX];
	FILE *file;
	int error;

	error = 0;

	LIST_INIT(&label_default_head);

	if (!issetugid() && getenv("MAC_CONFFILE") != NULL)
		filename = getenv("MAC_CONFFILE");
	else
		filename = MAC_CONFFILE;
	file = fopen(filename, "r");
	if (file == NULL)
		return (0);

	while (fgets(line, LINE_MAX, file)) {
		char *comment, *parse, *statement;

		if (line[strlen(line)-1] == '\n')
			line[strlen(line)-1] = '\0';
		else {
			if (ignore_errors)
				continue;
			fclose(file);
			error = EINVAL;
			goto just_return;
		}

		/* Remove any comment. */
		comment = line;
		parse = strsep(&comment, "#");

		/* Blank lines OK. */
		statement = next_token(&parse);
		if (statement == NULL)
			continue;

		if (strcmp(statement, "default_labels") == 0) {
			char *name, *labels;

			name = next_token(&parse);
			labels = next_token(&parse);
			if (name == NULL || labels == NULL ||
			    next_token(&parse) != NULL) {
				if (ignore_errors)
					continue;
				error = EINVAL;
				fclose(file);
				goto just_return;
			}

			if (mac_add_type(name, labels) == -1) {
				if (ignore_errors)
					continue;
				fclose(file);
				goto just_return;
			}
		} else if (strcmp(statement, "default_ifnet_labels") == 0 ||
		    strcmp(statement, "default_file_labels") == 0 ||
		    strcmp(statement, "default_process_labels") == 0) {
			char *labels, *type;

			if (strcmp(statement, "default_ifnet_labels") == 0)
				type = "ifnet";
			else if (strcmp(statement, "default_file_labels") == 0)
				type = "file";
			else if (strcmp(statement, "default_process_labels") ==
			    0)
				type = "process";

			labels = next_token(&parse);
			if (labels == NULL || next_token(&parse) != NULL) {
				if (ignore_errors)
					continue;
				error = EINVAL;
				fclose(file);
				goto just_return;
			}

			if (mac_add_type(type, labels) == -1) {
				if (ignore_errors)
					continue;
				fclose(file);
				goto just_return;
			}
		} else {
			if (ignore_errors)
				continue;
			fclose(file);
			error = EINVAL;
			goto just_return;
		}
	}

	fclose(file);

	internal_initialized = 1;

just_return:
	if (error != 0)
		mac_destroy_internal();
	return (error);
}

static int
mac_maybe_init_internal(void)
{

	if (!internal_initialized)
		return (mac_init_internal(1));
	else
		return (0);
}

int
mac_reload(void)
{

	if (internal_initialized)
		mac_destroy_internal();
	return (mac_init_internal(0));
}

int
mac_free(struct mac *mac)
{

	if (mac->m_string != NULL)
		free(mac->m_string);
	free(mac);

	return (0);
}

int
mac_from_text(struct mac **mac, const char *text)
{

	*mac = (struct mac *) malloc(sizeof(**mac));
	if (*mac == NULL)
		return (ENOMEM);

	(*mac)->m_string = strdup(text);
	if ((*mac)->m_string == NULL) {
		free(*mac);
		*mac = NULL;
		return (ENOMEM);
	}

	(*mac)->m_buflen = strlen((*mac)->m_string)+1;

	return (0);
}

int
mac_to_text(struct mac *mac, char **text)
{

	*text = strdup(mac->m_string);
	if (*text == NULL)
		return (ENOMEM);
	return (0);
}

int
mac_prepare(struct mac **mac, const char *elements)
{

	if (strlen(elements) >= MAC_MAX_LABEL_BUF_LEN)
		return (EINVAL);

	*mac = (struct mac *) malloc(sizeof(**mac));
	if (*mac == NULL)
		return (ENOMEM);

	(*mac)->m_string = malloc(MAC_MAX_LABEL_BUF_LEN);
	if ((*mac)->m_string == NULL) {
		free(*mac);
		*mac = NULL;
		return (ENOMEM);
	}

	strcpy((*mac)->m_string, elements);
	(*mac)->m_buflen = MAC_MAX_LABEL_BUF_LEN;

	return (0);
}

int
mac_prepare_type(struct mac **mac, const char *name)
{
	struct label_default *ld;
	int error;

	error = mac_maybe_init_internal();
	if (error != 0)
		return (error);

	for (ld = LIST_FIRST(&label_default_head); ld != NULL;
	    ld = LIST_NEXT(ld, ld_entries)) {
		if (strcmp(name, ld->ld_name) == 0)
			return (mac_prepare(mac, ld->ld_labels));
	}

	errno = ENOENT;
	return (-1);		/* XXXMAC: ENOLABEL */
}

int
mac_prepare_ifnet_label(struct mac **mac)
{

	return (mac_prepare_type(mac, "ifnet"));
}

int
mac_prepare_file_label(struct mac **mac)
{

	return (mac_prepare_type(mac, "file"));
}

int
mac_prepare_packet_label(struct mac **mac)
{

	return (mac_prepare_type(mac, "packet"));
}

int
mac_prepare_process_label(struct mac **mac)
{

	return (mac_prepare_type(mac, "process"));
}

/*
 * Simply test whether the TrustedBSD/MAC MIB tree is present; if so,
 * return 1 to indicate that the system has MAC enabled overall or for
 * a given policy.
 */
int
mac_is_present(const char *policyname)
{
	int mib[5];
	size_t siz;
	char *mibname;
	int error;

	if (policyname != NULL) {
		if (policyname[strcspn(policyname, ".=")] != '\0') {
			errno = EINVAL;
			return (-1);
		}
		mibname = malloc(sizeof("security.mac.") - 1 +
		    strlen(policyname) + sizeof(".enabled"));
		if (mibname == NULL)
			return (-1);
		strcpy(mibname, "security.mac.");
		strcat(mibname, policyname);
		strcat(mibname, ".enabled");
		siz = 5;
		error = sysctlnametomib(mibname, mib, &siz);
		free(mibname);
	} else {
		siz = 3;
		error = sysctlnametomib("security.mac", mib, &siz);
	}
	if (error == -1) {
		switch (errno) {
		case ENOTDIR:
		case ENOENT:
			return (0);
		default:
			return (error);
		}
	}
	return (1);
}