#include <errno.h>
#include <fnmatch.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <log/log.h>
#include <selinux/android.h>
#include <selinux/label.h>

#include "android_internal.h"
#include "callbacks.h"

#ifdef __ANDROID_VNDK__
#ifndef LOG_EVENT_STRING
#define LOG_EVENT_STRING(...)
#endif  // LOG_EVENT_STRING
#endif  // __ANDROID_VNDK__

static const path_alts_t service_context_paths = { .paths = {
	{
		"/system/etc/selinux/plat_service_contexts",
		"/plat_service_contexts"
	},
	{
		"/system_ext/etc/selinux/system_ext_service_contexts",
		"/system_ext_service_contexts"
	},
	{
		"/product/etc/selinux/product_service_contexts",
		"/product_service_contexts"
	},
	{
		"/vendor/etc/selinux/vendor_service_contexts",
		"/vendor_service_contexts"
	},
	{
		"/odm/etc/selinux/odm_service_contexts",
	}
}};

static const path_alts_t hwservice_context_paths = { .paths = {
	{
		"/system/etc/selinux/plat_hwservice_contexts",
		"/plat_hwservice_contexts"
	},
	{
		"/system_ext/etc/selinux/system_ext_hwservice_contexts",
		"/system_ext_hwservice_contexts"
	},
	{
		"/product/etc/selinux/product_hwservice_contexts",
		"/product_hwservice_contexts"
	},
	{
		"/vendor/etc/selinux/vendor_hwservice_contexts",
		"/vendor_hwservice_contexts"
	},
	{
		"/odm/etc/selinux/odm_hwservice_contexts",
		"/odm_hwservice_contexts"
	},
}};

static const path_alts_t vndservice_context_paths = { .paths = {
	{
		"/vendor/etc/selinux/vndservice_contexts",
		"/vndservice_contexts"
	}
}};

static const path_alts_t keystore2_context_paths = { .paths = {
	{
		"/system/etc/selinux/plat_keystore2_key_contexts",
		"/plat_keystore2_key_contexts"
	},
	{
		"/system_ext/etc/selinux/system_ext_keystore2_key_contexts",
		"/system_ext_keystore2_key_contexts"
	},
	{
		"/product/etc/selinux/product_keystore2_key_contexts",
		"/product_keystore2_key_contexts"
	},
	{
		"/vendor/etc/selinux/vendor_keystore2_key_contexts",
		"/vendor_keystore2_key_contexts"
	}
}};

static const path_alts_t tee_service_context_paths = { .paths = {
	{
		"/system/etc/selinux/plat_tee_service_contexts",
		"/plat_tee_service_contexts"
	},
	{
		"/system_ext/etc/selinux/system_ext_tee_service_contexts",
		"/system_ext_tee_service_contexts"
	},
	{
		"/product/etc/selinux/product_tee_service_contexts",
		"/product_tee_service_contexts"
	},
	{
		"/vendor/etc/selinux/vendor_tee_service_contexts",
		"/vendor_tee_service_contexts"
	}
}};

size_t find_existing_files(
		const path_alts_t *path_sets,
		const char* paths[MAX_CONTEXT_PATHS])
{
	return find_existing_files_with_partitions(
		path_sets,
		paths,
		NULL
	);
}

size_t find_existing_files_with_partitions(
		const path_alts_t *path_sets,
		const char* paths[MAX_CONTEXT_PATHS],
		const char* partitions[MAX_CONTEXT_PATHS])
{
	size_t i, j, len = 0;
	for (i = 0; i < MAX_CONTEXT_PATHS; i++) {
		for (j = 0; j < MAX_ALT_CONTEXT_PATHS; j++) {
			const char* file = path_sets->paths[i][j];
			if (file && access(file, R_OK) != -1) {
				if (partitions) {
					partitions[len] = path_sets->partitions[i];
				}
				paths[len++] = file;
				/* Within each set, only the first valid entry is used */
				break;
			}
		}
	}
	return len;
}

void paths_to_opts(const char* paths[MAX_CONTEXT_PATHS],
		size_t npaths,
		struct selinux_opt* const opts)
{
	for (size_t i = 0; i < npaths; i++) {
		opts[i].type = SELABEL_OPT_PATH;
		opts[i].value = paths[i];
	}
}

struct selabel_handle* initialize_backend(
		unsigned int backend,
		const char* name,
		const struct selinux_opt* opts,
		size_t nopts)
{
		struct selabel_handle* sehandle;

		sehandle = selabel_open(backend, opts, nopts);

		if (!sehandle) {
				selinux_log(SELINUX_ERROR, "%s: Error getting %s handle (%s)\n",
								__FUNCTION__, name, strerror(errno));
				return NULL;
		}
		selinux_log(SELINUX_INFO, "SELinux: Loaded %s context from:\n", name);
		for (unsigned i = 0; i < nopts; i++) {
			if (opts[i].type == SELABEL_OPT_PATH)
				selinux_log(SELINUX_INFO, "		%s\n", opts[i].value);
		}
		return sehandle;
}

struct selabel_handle* context_handle(
		unsigned int backend,
		const path_alts_t *context_paths,
		const char *name)
{
	const char* existing_paths[MAX_CONTEXT_PATHS];
	struct selinux_opt opts[MAX_CONTEXT_PATHS];
	int size = 0;

	size = find_existing_files(context_paths, existing_paths);
	paths_to_opts(existing_paths, size, opts);

	return initialize_backend(backend, name, opts, size);
}

struct selabel_handle* selinux_android_service_context_handle(void)
{
	return context_handle(SELABEL_CTX_ANDROID_SERVICE, &service_context_paths, "service");
}

struct selabel_handle* selinux_android_hw_service_context_handle(void)
{
	return context_handle(SELABEL_CTX_ANDROID_SERVICE, &hwservice_context_paths, "hwservice");
}

struct selabel_handle* selinux_android_vendor_service_context_handle(void)
{
	return context_handle(SELABEL_CTX_ANDROID_SERVICE, &vndservice_context_paths, "vndservice");
}

struct selabel_handle* selinux_android_keystore2_key_context_handle(void)
{
	return context_handle(SELABEL_CTX_ANDROID_KEYSTORE2_KEY, &keystore2_context_paths, "keystore2");
}

struct selabel_handle* selinux_android_tee_service_context_handle(void)
{
	return context_handle(SELABEL_CTX_ANDROID_SERVICE, &tee_service_context_paths, "tee_service");
}

/* The contents of these paths are encrypted on FBE devices until user
 * credentials are presented (filenames inside are mangled), so we need
 * to delay restorecon of those until vold explicitly requests it. */
// NOTE: these paths need to be kept in sync with vold
#define DATA_SYSTEM_CE_PATH "/data/system_ce"
#define DATA_VENDOR_CE_PATH "/data/vendor_ce"
#define DATA_MISC_CE_PATH "/data/misc_ce"

/* The path prefixes of package data directories. */
#define DATA_DATA_PATH "/data/data"
#define DATA_USER_PATH "/data/user"
#define DATA_USER_DE_PATH "/data/user_de"
#define DATA_MISC_DE_PATH "/data/misc_de"
#define DATA_STORAGE_AREA_PATH "/data/storage_area"
#define SDK_SANDBOX_DATA_CE_PATH "/data/misc_ce/*/sdksandbox"
#define SDK_SANDBOX_DATA_DE_PATH "/data/misc_de/*/sdksandbox"

#define EXPAND_MNT_PATH "/mnt/expand/\?\?\?\?\?\?\?\?-\?\?\?\?-\?\?\?\?-\?\?\?\?-\?\?\?\?\?\?\?\?\?\?\?\?"
#define EXPAND_USER_PATH EXPAND_MNT_PATH "/user"
#define EXPAND_USER_DE_PATH EXPAND_MNT_PATH "/user_de"
#define EXPAND_SDK_CE_PATH EXPAND_MNT_PATH "/misc_ce/*/sdksandbox"
#define EXPAND_SDK_DE_PATH EXPAND_MNT_PATH "/misc_de/*/sdksandbox"

#define DATA_DATA_PREFIX DATA_DATA_PATH "/"
#define DATA_USER_PREFIX DATA_USER_PATH "/"
#define DATA_USER_DE_PREFIX DATA_USER_DE_PATH "/"
#define DATA_STORAGE_AREA_PREFIX DATA_STORAGE_AREA_PATH "/"
#define DATA_MISC_CE_PREFIX DATA_MISC_CE_PATH "/"
#define DATA_MISC_DE_PREFIX DATA_MISC_DE_PATH "/"
#define EXPAND_MNT_PATH_PREFIX EXPAND_MNT_PATH "/"

bool is_app_data_path(const char *pathname) {
	int flags = FNM_LEADING_DIR|FNM_PATHNAME;
#ifdef SELINUX_FLAGS_DATA_DATA_IGNORE
	if (!strcmp(pathname, DATA_DATA_PATH)) {
		return true;
	}
#endif
	return (!strncmp(pathname, DATA_DATA_PREFIX, sizeof(DATA_DATA_PREFIX)-1) ||
		!strncmp(pathname, DATA_USER_PREFIX, sizeof(DATA_USER_PREFIX)-1) ||
		!strncmp(pathname, DATA_USER_DE_PREFIX, sizeof(DATA_USER_DE_PREFIX)-1) ||
		!strncmp(pathname, DATA_STORAGE_AREA_PREFIX, sizeof(DATA_STORAGE_AREA_PREFIX)-1) ||
		!fnmatch(EXPAND_USER_PATH, pathname, flags) ||
		!fnmatch(EXPAND_USER_DE_PATH, pathname, flags) ||
		!fnmatch(SDK_SANDBOX_DATA_CE_PATH, pathname, flags) ||
		!fnmatch(SDK_SANDBOX_DATA_DE_PATH, pathname, flags) ||
		!fnmatch(EXPAND_SDK_CE_PATH, pathname, flags) ||
		!fnmatch(EXPAND_SDK_DE_PATH, pathname, flags));
}

bool is_credential_encrypted_path(const char *pathname) {
	return !strncmp(pathname, DATA_SYSTEM_CE_PATH, sizeof(DATA_SYSTEM_CE_PATH)-1) ||
	       !strncmp(pathname, DATA_MISC_CE_PATH, sizeof(DATA_MISC_CE_PATH)-1) ||
	       !strncmp(pathname, DATA_VENDOR_CE_PATH, sizeof(DATA_VENDOR_CE_PATH)-1);
}

/*
 * Extract the userid from a path.
 * On success, pathname is updated past the userid.
 * Returns 0 on success, -1 on error
 */
static int extract_userid(const char **pathname, unsigned int *userid)
{
	char *end = NULL;

	errno = 0;
	*userid = strtoul(*pathname, &end, 10);
	if (errno) {
		selinux_log(SELINUX_ERROR, "SELinux: Could not parse userid %s: %s.\n",
			*pathname, strerror(errno));
		return -1;
	}
	if (*pathname == end) {
		return -1;
	}
	if (*userid > 1000) {
		return -1;
	}
	*pathname = end;
	return 0;
}

int extract_pkgname_and_userid(const char *pathname, char **pkgname, unsigned int *userid)
{
	char *end = NULL;

	if (pkgname == NULL || *pkgname != NULL || userid == NULL) {
		errno = EINVAL;
		return -2;
	}

	/* Skip directory prefix before package name. */
	if (!strncmp(pathname, DATA_DATA_PREFIX, sizeof(DATA_DATA_PREFIX)-1)) {
		pathname += sizeof(DATA_DATA_PREFIX) - 1;
	} else if (!strncmp(pathname, DATA_USER_PREFIX, sizeof(DATA_USER_PREFIX)-1)) {
		pathname += sizeof(DATA_USER_PREFIX) - 1;
		int rc = extract_userid(&pathname, userid);
		if (rc)
			return -1;
		if (*pathname == '/')
			pathname++;
		else
			return -1;
	} else if (!strncmp(pathname, DATA_USER_DE_PREFIX, sizeof(DATA_USER_DE_PREFIX)-1)) {
		pathname += sizeof(DATA_USER_DE_PREFIX) - 1;
		int rc = extract_userid(&pathname, userid);
		if (rc)
			return -1;
		if (*pathname == '/')
			pathname++;
		else
			return -1;
	} else if (!strncmp(pathname, DATA_STORAGE_AREA_PREFIX, sizeof(DATA_STORAGE_AREA_PREFIX)-1)) {
		pathname += sizeof(DATA_STORAGE_AREA_PREFIX) - 1;
		int rc = extract_userid(&pathname, userid);
		if (rc)
			return -1;
		if (*pathname == '/')
			pathname++;
		else
			return -1;
	} else if (!fnmatch(EXPAND_USER_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME)) {
		pathname += sizeof(EXPAND_USER_PATH);
		int rc = extract_userid(&pathname, userid);
		if (rc)
			return -1;
		if (*pathname == '/')
			pathname++;
		else
			return -1;
	} else if (!fnmatch(EXPAND_USER_DE_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME)) {
		pathname += sizeof(EXPAND_USER_DE_PATH);
		int rc = extract_userid(&pathname, userid);
		if (rc)
			return -1;
		if (*pathname == '/')
			pathname++;
		else
			return -1;
	} else if (!strncmp(pathname, DATA_MISC_CE_PREFIX, sizeof(DATA_MISC_CE_PREFIX)-1)) {
		pathname += sizeof(DATA_MISC_CE_PREFIX) - 1;
		int rc = extract_userid(&pathname, userid);
		if (rc)
			return -1;
		if (!strncmp(pathname, "/sdksandbox/", sizeof("/sdksandbox/")-1))
			pathname += sizeof("/sdksandbox/") - 1;
		else
			return -1;
	} else if (!strncmp(pathname, DATA_MISC_DE_PREFIX, sizeof(DATA_MISC_DE_PREFIX)-1)) {
		pathname += sizeof(DATA_MISC_DE_PREFIX) - 1;
		int rc = extract_userid(&pathname, userid);
		if (rc)
			return -1;
		if (!strncmp(pathname, "/sdksandbox/", sizeof("/sdksandbox/")-1))
			pathname += sizeof("/sdksandbox/") - 1;
		else
			return -1;
	} else if (!fnmatch(EXPAND_SDK_CE_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME)) {
		pathname += sizeof(EXPAND_MNT_PATH_PREFIX) - 1;
		pathname += sizeof("misc_ce/") - 1;
		int rc = extract_userid(&pathname, userid);
		if (rc)
			return -1;
		if (!strncmp(pathname, "/sdksandbox/", sizeof("/sdksandbox/")-1))
			pathname += sizeof("/sdksandbox/") - 1;
		else
			return -1;
	} else if (!fnmatch(EXPAND_SDK_DE_PATH, pathname, FNM_LEADING_DIR|FNM_PATHNAME)) {
		pathname += sizeof(EXPAND_MNT_PATH_PREFIX) - 1;
		pathname += sizeof("misc_de/") - 1;
		int rc = extract_userid(&pathname, userid);
		if (rc)
			return -1;
		if (!strncmp(pathname, "/sdksandbox/", sizeof("/sdksandbox/")-1))
			pathname += sizeof("/sdksandbox/") - 1;
		else
			return -1;
	} else
		return -1;

	if (!(*pathname))
		return -1;

	*pkgname = strdup(pathname);
	if (!(*pkgname))
		return -2;

	// Trim pkgname.
	for (end = *pkgname; *end && *end != '/'; end++);
	*end = '\0';

	return 0;
}

static void __selinux_log_callback(bool add_to_event_log, int type, const char *fmt, va_list ap) {
	int priority;
	char *strp;

	switch(type) {
	case SELINUX_WARNING:
		priority = ANDROID_LOG_WARN;
		break;
	case SELINUX_INFO:
		priority = ANDROID_LOG_INFO;
		break;
	default:
		priority = ANDROID_LOG_ERROR;
		break;
	}

	int len = vasprintf(&strp, fmt, ap);
	if (len < 0) {
		return;
	}

	/* libselinux log messages usually contain a new line character, while
	 * Android LOG() does not expect it. Remove it to avoid empty lines in
	 * the log buffers.
	 */
	if (len > 0 && strp[len - 1] == '\n') {
		strp[len - 1] = '\0';
	}
	LOG_PRI(priority, "SELinux", "%s", strp);
	if (add_to_event_log) {
		LOG_EVENT_STRING(AUDITD_LOG_TAG, strp);
	}
	free(strp);
}

int selinux_log_callback(int type, const char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	__selinux_log_callback(true, type, fmt, ap);
	va_end(ap);
	return 0;
}

int selinux_vendor_log_callback(int type, const char *fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	__selinux_log_callback(false, type, fmt, ap);
	va_end(ap);
	return 0;
}
