#include <sys/types.h>
#include <sys/mac.h>
#include <sys/queue.h>
#include <sys/syslimits.h>

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

#define	MAC_CONF	"/etc/mac.conf"
#define	MAC_POLICY_CONF	"/etc/mac_policy.conf"
#define	MAX_TOKENS	100

struct mac_module {
	char			 mm_policyname[PATH_MAX];
	char			 mm_sharedobject[PATH_MAX];
	void			*mm_dlhandle;

	int			(*mm_init)(char *policyname, char *conffile,
				    int linenum);
	int			(*mm_destroy)(char *policyname);
	int			(*mm_from_text)(char *policyname,
				    const char *text_p, struct mac *label);
	int			(*mm_get_label_for_object)(char *policyname,
				    const char *objectname, struct mac *label);
	int			(*mm_to_text)(char *policyname,
				    struct mac *label, char **text);

	LIST_ENTRY(mac_module)	mm_list;
};

static int mac_loaded = 0;
LIST_HEAD(, mac_module) mac_module_list_head;

static struct mac_module *
mac_module_configure(char *conffile, int linenum, char *policyname,
    char *sharedobject)
{
	struct mac_module *module;
	int error;

/*
	printf("mac_module_configure: %s %d %s %s\n", conffile, linenum,
	    policyname, sharedobject);
*/

	module = (struct mac_module *) malloc(sizeof(*module));
	if (module == NULL) {
		perror("malloc");
		return (NULL);
	}

	if (strlcpy(module->mm_policyname, policyname,
	    sizeof(module->mm_policyname)) >= sizeof(module->mm_policyname)) {
		fprintf(stderr, "%s:%d: Policy name too long.\n", conffile,
		    linenum);
		free(module);
		return (NULL);
	}

	if (strlcpy(module->mm_sharedobject, sharedobject,
	    sizeof(module->mm_sharedobject)) >=
	    sizeof(module->mm_sharedobject)) {
		fprintf(stderr, "%s:%d: Shared object name too long.\n",
		    conffile, linenum);
		free(module);
		return (NULL);
	}

	module->mm_dlhandle = dlopen(module->mm_sharedobject, RTLD_NOW);
	if (module->mm_dlhandle == NULL) {
		fprintf(stderr, "%s: %s\n", module->mm_sharedobject,
		    dlerror());
		free(module);
		return (NULL);
	}

	module->mm_init = dlsym(module->mm_dlhandle, "mac_module_init");
	module->mm_destroy = dlsym(module->mm_dlhandle, "mac_module_destroy");
	module->mm_from_text = dlsym(module->mm_dlhandle,
	    "mac_module_from_text");
	module->mm_get_label_for_object = dlsym(module->mm_dlhandle,
	    "mac_module_get_label_for_object");
	module->mm_to_text = dlsym(module->mm_dlhandle, "mac_module_to_text");

	if (module->mm_init != NULL) {
		error = (module->mm_init)(policyname, conffile, linenum);
		if (error != 0) {
			dlclose(module->mm_dlhandle);
			free(module);
			return (NULL);
		}
	}

	return (module);
}

static void
mac_unload_mac_conf(void)
{
	struct mac_module *module;

	/* Disassemble list, releasing each module. */

	while (!LIST_EMPTY(&mac_module_list_head)) {
		module = LIST_FIRST(&mac_module_list_head);

		LIST_REMOVE(module, mm_list);

		if (module->mm_destroy != NULL)
			(module->mm_destroy)(module->mm_policyname);

		dlclose(module->mm_dlhandle);

		free(module);
	}

	mac_loaded = 0;
}

static int
mac_load_mac_conf(char *conffile)
{
	struct mac_module *module;
	FILE *mac_conf_file;
	char **ap, *argv[MAX_TOKENS], *inputstring;
	char line[LINE_MAX];
	int argc, i, linenum;

	if (mac_loaded)
		mac_unload_mac_conf();

	mac_conf_file = fopen(conffile, "r");
	if (mac_conf_file == NULL) {
		perror(conffile);
		return (-1);
	}

	mac_loaded = 1;		/* So that we can bail later. */
	linenum = 0;

	while (fgets(line, LINE_MAX, mac_conf_file)) {
		linenum++;
		if (line[strlen(line)-1] == '\n')
			line[strlen(line)-1] = '\0';

		/* Tokenize. */
		inputstring = line;
		argc = 0;
		for (ap = argv; (*ap = strsep(&inputstring, " \t")) != NULL;) {
			if (**ap != '\0') {
				argc++;
				ap++;
				if (argc > MAX_TOKENS)
					break;
			}
		}

		/* Trim any comment. */
		for (i = 0; i < argc; i++)
			if (argv[i][0] == '#')
				argc = i;

		/* Skip blank lines. */
		if (argc == 0)
			continue;

/*
		for (i = 0; i < argc; i++)
			printf("argv[%d]: %s\n", i, argv[i]);
*/

		/* Test for various sorts of statements. */
		if (strcmp(argv[0], "policy") == 0) {
			if (argc != 3) {
				fprintf(stderr,
				    "%s:%d: Unexpected token \"%s\"\n",
				    conffile, linenum, argv[3]);
				mac_unload_mac_conf();
				return (-1);
			}

			module = mac_module_configure(conffile, linenum,
			    argv[1], argv[2]);
			if (module == NULL) {
				mac_unload_mac_conf();
				return (-1);
			}

			LIST_INSERT_HEAD(&mac_module_list_head, module,
			    mm_list);

		} else {
			fprintf(stderr, "%s:%d: Unknown token \"%s\"\n",
			    conffile, linenum, argv[0]);
			mac_unload_mac_conf();
			return (-1);
		}
	}

	if (ferror(mac_conf_file)) {
		perror(conffile);
		mac_unload_mac_conf();
		fclose(mac_conf_file);
		return (-1);
	}

/*
	printf("read mac.conf\n");
*/

	fclose(mac_conf_file);

	return (0);
}

mac_t
mac_from_text(const char *text_p)
{
	struct mac_module *module;
	struct mac *label;
	int error;

	label = (struct mac *) malloc(sizeof(*label));
	if (label == NULL) {
		errno = ENOMEM;
		return (NULL);
	}

	bzero(label, sizeof(*label));
	label->m_macflags = MAC_FLAG_INITIALIZED;

	/*
	 * XXX: Really, this should parse the string into tokens, pass
	 * strings off to the appropriate policy, and detect if a policy is
	 * named but not available.
	 */

	LIST_FOREACH(module, &mac_module_list_head, mm_list) {
		if (module->mm_from_text != NULL) {
			error = (module->mm_from_text)(module->mm_policyname,
			    text_p, label);
			if (error) {
				free(label);
				errno = error;
				return (NULL);
			}
		}
	}

	return (label);
}

mac_t
mac_get_label_for_object(char *objectname)
{
	struct mac_module *module;
	struct mac *label;
	int error;

	label = (struct mac *) malloc(sizeof(*label));
	if (label == NULL) {
		errno = ENOMEM;
		return (NULL);
	}

	bzero(label, sizeof(*label));
	label->m_macflags = MAC_FLAG_INITIALIZED;

	LIST_FOREACH(module, &mac_module_list_head, mm_list) {
		if (module->mm_get_label_for_object != NULL) {
			error = (module->mm_get_label_for_object)(
			    module->mm_policyname, objectname, label);
			if (error) {
				free(label);
				errno = error;
				return (NULL);
			}
		}

	}

	return (label);
}

char *
mac_to_text(struct mac *label, size_t *len_p)
{
	struct mac_module *module;
	char *string, *tmpmodule, *tmpcat;
	int error;

	string = NULL;

	LIST_FOREACH(module, &mac_module_list_head, mm_list) {
		if (module->mm_to_text != NULL) {
			error = (module->mm_to_text)(module->mm_policyname,
			    label, &tmpmodule);
			if (error) {
				free(string);
				errno = error;
				return (NULL);
			}
			if (string == NULL) {
				tmpcat = tmpmodule;
			} else {
				if (asprintf(&tmpcat, "%s,%s", string,
				    tmpmodule) == -1) {
					free(string);
					errno = ENOMEM;
					return (NULL);
				}
			}
			free(string);
			string = tmpcat;
		}
	}

	if (string == NULL)
		string = strdup("");

	if (len_p != NULL)
		*len_p = strlen(string);

	return (string);
}

int
mac_application_start(void)
{

	if (mac_loaded) {
		fprintf(stderr, "mac_application_start() called again.\n");
		return (-1);
	}

	LIST_INIT(&mac_module_list_head);

	if (mac_load_mac_conf(MAC_CONF) == -1)
		return (-1);

	return (0);
}

int
mac_application_stop(void)
{

	if (!mac_loaded) {
		fprintf(stderr, "mac_application_stop() without start.\n");
		return (-1);
	}

	mac_unload_mac_conf();

	return (0);
}
