// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2019  Intel Corporation. All rights reserved.
 *
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#include <sys/stat.h>

#include <ell/ell.h>

#include "mesh/mesh-defs.h"

#include "mesh/dbus.h"
#include "mesh/node.h"
#include "mesh/keyring.h"

static const char *dev_key_dir = "/dev_keys";
static const char *app_key_dir = "/app_keys";
static const char *net_key_dir = "/net_keys";

static int open_key_file(struct mesh_node *node, const char *key_dir,
							uint16_t idx, int flags)
{
	const char *node_path;
	char fname[PATH_MAX];
	int ret;

	if (!node)
		return -1;

	node_path = node_get_storage_dir(node);

	if (flags & O_CREAT) {
		ret = snprintf(fname, PATH_MAX, "%s%s", node_path, key_dir);
		if (ret < 0)
			return -1;

		if (mkdir(fname, 0755) != 0 && errno != EEXIST)
			l_error("Failed to create dir(%d): %s", errno, fname);
	}

	ret = snprintf(fname, PATH_MAX, "%s%s/%3.3x", node_path, key_dir, idx);
	if (ret < 0)
		return -1;

	if (flags & O_CREAT)
		return open(fname, flags, 0600);
	else
		return open(fname, flags);
}

bool keyring_put_net_key(struct mesh_node *node, uint16_t net_idx,
					const struct keyring_net_key *key)
{
	bool result = false;
	int fd;

	if (!key)
		return false;

	fd = open_key_file(node, net_key_dir, net_idx,
					O_WRONLY | O_CREAT | O_TRUNC);

	if (fd < 0)
		return false;

	if (write(fd, key, sizeof(*key)) == sizeof(*key))
		result = true;

	close(fd);

	return result;
}

bool keyring_put_app_key(struct mesh_node *node, uint16_t app_idx,
				uint16_t net_idx, struct keyring_app_key *key)
{
	bool result = false;
	int fd;

	if (!key)
		return false;

	fd = open_key_file(node, app_key_dir, app_idx, O_RDWR);

	if (fd >= 0) {
		struct keyring_app_key old_key;

		if (read(fd, &old_key, sizeof(old_key)) == sizeof(old_key)) {
			if (old_key.net_idx != net_idx) {
				close(fd);
				return false;
			}
		}

		lseek(fd, 0, SEEK_SET);
	} else
		fd = open_key_file(node, app_key_dir, app_idx,
						O_WRONLY | O_CREAT | O_TRUNC);

	if (fd < 0)
		return false;

	if (write(fd, key, sizeof(*key)) == sizeof(*key))
		result = true;

	close(fd);

	return result;
}

static void finalize(int dir_fd, const char *fname, uint16_t net_idx)
{
	struct keyring_app_key key;
	int fd;

	fd = openat(dir_fd, fname, O_RDWR);

	if (fd < 0)
		return;

	if (read(fd, &key, sizeof(key)) != sizeof(key) ||
						key.net_idx != net_idx)
		goto done;

	l_debug("Finalize %s", fname);
	memcpy(key.old_key, key.new_key, 16);
	lseek(fd, 0, SEEK_SET);

	if (write(fd, &key, sizeof(key)) != sizeof(key))
		goto done;

done:
	close(fd);
}

bool keyring_finalize_app_keys(struct mesh_node *node, uint16_t net_idx)
{
	const char *node_path;
	char key_dir[PATH_MAX];
	DIR *dir;
	int ret, dir_fd;
	struct dirent *entry;

	if (!node)
		return false;

	node_path = node_get_storage_dir(node);

	ret = snprintf(key_dir, PATH_MAX, "%s%s", node_path, app_key_dir);
	if (ret < 0)
		return false;

	dir = opendir(key_dir);
	if (!dir) {
		if (errno == ENOENT)
			return true;

		l_error("Failed to open AppKey storage directory: %s", key_dir);
		return false;
	}

	dir_fd = dirfd(dir);

	while ((entry = readdir(dir)) != NULL) {
		/* AppKeys are stored in regular files */
		if (entry->d_type == DT_REG)
			finalize(dir_fd, entry->d_name, net_idx);
	}

	closedir(dir);

	return true;
}

bool keyring_put_remote_dev_key(struct mesh_node *node, uint16_t unicast,
				uint8_t count, const uint8_t dev_key[16])
{
	const char *node_path;
	char key_file[PATH_MAX];
	bool result = true;
	int ret, fd, i;

	if (!IS_UNICAST_RANGE(unicast, count))
		return false;

	if (!node)
		return false;

	node_path = node_get_storage_dir(node);

	ret = snprintf(key_file, PATH_MAX, "%s%s", node_path, dev_key_dir);
	if (ret < 0)
		return false;

	if (mkdir(key_file, 0755) != 0 && errno != EEXIST)
		l_error("Failed to create dir(%d): %s", errno, key_file);

	for (i = 0; i < count; i++) {
		ret = snprintf(key_file, PATH_MAX, "%s%s/%4.4x", node_path,
						dev_key_dir, unicast + i);
		if (ret < 0)
			return false;

		l_debug("Put Dev Key %s", key_file);

		fd = open(key_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
		if (fd >= 0) {
			if (write(fd, dev_key, 16) != 16)
				result = false;

			close(fd);
		} else
			result = false;
	}

	return result;
}

static bool get_key(struct mesh_node *node, const char *key_dir,
					uint16_t key_idx, void *key, ssize_t sz)
{
	bool result = false;
	int fd;

	if (!key)
		return false;

	fd = open_key_file(node, key_dir, key_idx, O_RDONLY);

	if (fd >= 0) {
		if (read(fd, key, sz) == sz)
			result = true;

		close(fd);
	}

	return result;
}

bool keyring_get_net_key(struct mesh_node *node, uint16_t net_idx,
						struct keyring_net_key *key)
{
	return get_key(node, net_key_dir, net_idx, key, sizeof(*key));
}

bool keyring_get_app_key(struct mesh_node *node, uint16_t app_idx,
						struct keyring_app_key *key)
{
	return get_key(node, app_key_dir, app_idx, key, sizeof(*key));
}

bool keyring_get_remote_dev_key(struct mesh_node *node, uint16_t unicast,
							uint8_t dev_key[16])
{
	const char *node_path;
	char key_file[PATH_MAX];
	bool result = false;
	int ret, fd;

	if (!IS_UNICAST(unicast))
		return false;

	if (!node)
		return false;

	node_path = node_get_storage_dir(node);

	ret = snprintf(key_file, PATH_MAX, "%s%s/%4.4x", node_path, dev_key_dir,
								unicast);
	if (ret < 0)
		return false;

	fd = open(key_file, O_RDONLY);
	if (fd >= 0) {
		if (read(fd, dev_key, 16) == 16)
			result = true;

		close(fd);
	}

	return result;
}

bool keyring_del_net_key(struct mesh_node *node, uint16_t net_idx)
{
	const char *node_path;
	char key_file[PATH_MAX];
	int ret;

	if (!node)
		return false;

	node_path = node_get_storage_dir(node);
	ret = snprintf(key_file, PATH_MAX, "%s%s/%3.3x", node_path, net_key_dir,
								net_idx);
	if (ret < 0)
		return false;

	l_debug("RM Net Key %s", key_file);
	remove(key_file);

	/* TODO: See if it is easiest to delete all bound App keys here */
	/* TODO: see nftw() */

	return true;
}

bool keyring_del_app_key(struct mesh_node *node, uint16_t app_idx)
{
	const char *node_path;
	char key_file[PATH_MAX];
	int ret;

	if (!node)
		return false;

	node_path = node_get_storage_dir(node);
	ret = snprintf(key_file, PATH_MAX, "%s%s/%3.3x", node_path, app_key_dir,
								app_idx);
	if (ret < 0)
		return false;

	l_debug("RM App Key %s", key_file);
	remove(key_file);

	return true;
}

bool keyring_del_remote_dev_key(struct mesh_node *node, uint16_t unicast,
								uint8_t count)
{
	const char *node_path;
	char key_file[PATH_MAX];
	int ret, i;

	if (!IS_UNICAST_RANGE(unicast, count))
		return false;

	if (!node)
		return false;

	node_path = node_get_storage_dir(node);

	for (i = 0; i < count; i++) {
		ret = snprintf(key_file, PATH_MAX, "%s%s/%4.4x", node_path,
						dev_key_dir, unicast + i);
		if (ret < 0)
			return false;

		l_debug("RM Dev Key %s", key_file);
		remove(key_file);
	}

	return true;
}

bool keyring_del_remote_dev_key_all(struct mesh_node *node, uint16_t unicast)
{
	uint8_t dev_key[16];
	uint8_t test_key[16];
	uint8_t cnt = 1;

	if (!keyring_get_remote_dev_key(node, unicast, dev_key))
		return false;

	while (keyring_get_remote_dev_key(node, unicast + cnt, test_key)) {
		if (memcmp(dev_key, test_key, sizeof(dev_key)))
			break;

		cnt++;
	}

	if (cnt > 1)
		return keyring_del_remote_dev_key(node, unicast + 1, cnt - 1);

	return true;
}

static DIR *open_key_dir(const char *node_path, const char *key_dir_name)
{
	char dir_path[PATH_MAX];
	DIR *key_dir;
	int ret;

	ret = snprintf(dir_path, PATH_MAX, "%s%s", node_path, key_dir_name);
	if (ret < 0)
		return NULL;

	key_dir = opendir(dir_path);
	if (!key_dir)
		l_error("Failed to open keyring storage directory: %s",
								dir_path);

	return key_dir;
}

static int open_key_dir_entry(int dir_fd, struct dirent *entry,
							uint8_t fname_len)
{
	if (entry->d_type != DT_REG)
		return -1;

	/* Check the file name length */
	if (strlen(entry->d_name) != fname_len)
		return -1;

	return openat(dir_fd, entry->d_name, O_RDONLY);
}

static void append_old_key(struct l_dbus_message_builder *builder,
							const uint8_t key[16])
{
	l_dbus_message_builder_enter_dict(builder, "sv");
	l_dbus_message_builder_append_basic(builder, 's', "OldKey");
	l_dbus_message_builder_enter_variant(builder, "ay");
	dbus_append_byte_array(builder, key, 16);
	l_dbus_message_builder_leave_variant(builder);
	l_dbus_message_builder_leave_dict(builder);
}

static void build_app_keys_reply(const char *node_path,
					struct l_dbus_message_builder *builder,
					uint16_t net_idx, uint8_t phase)
{
	DIR *key_dir;
	int key_dir_fd;
	struct dirent *entry;

	key_dir = open_key_dir(node_path, app_key_dir);
	if (!key_dir)
		return;

	key_dir_fd = dirfd(key_dir);

	l_dbus_message_builder_enter_dict(builder, "sv");
	l_dbus_message_builder_append_basic(builder, 's', "AppKeys");
	l_dbus_message_builder_enter_variant(builder, "a(qaya{sv})");
	l_dbus_message_builder_enter_array(builder, "(qaya{sv})");

	while ((entry = readdir(key_dir)) != NULL) {
		struct keyring_app_key key;
		int fd = open_key_dir_entry(key_dir_fd, entry, 3);

		if (fd < 0)
			continue;

		if (read(fd, &key, sizeof(key)) != sizeof(key) ||
						key.net_idx != net_idx) {
			close(fd);
			continue;
		}

		close(fd);

		l_dbus_message_builder_enter_struct(builder, "qaya{sv}");

		l_dbus_message_builder_append_basic(builder, 'q', &key.app_idx);
		dbus_append_byte_array(builder, key.new_key, 16);

		l_dbus_message_builder_enter_array(builder, "{sv}");

		if (phase != KEY_REFRESH_PHASE_NONE)
			append_old_key(builder, key.old_key);

		l_dbus_message_builder_leave_array(builder);
		l_dbus_message_builder_leave_struct(builder);
	}

	l_dbus_message_builder_leave_array(builder);
	l_dbus_message_builder_leave_variant(builder);
	l_dbus_message_builder_leave_dict(builder);

	closedir(key_dir);
}

static bool build_net_keys_reply(const char *node_path,
					struct l_dbus_message_builder *builder)
{
	DIR *key_dir;
	int key_dir_fd;
	struct dirent *entry;
	bool result = false;

	key_dir = open_key_dir(node_path, net_key_dir);
	if (!key_dir)
		return false;

	key_dir_fd = dirfd(key_dir);

	l_dbus_message_builder_enter_dict(builder, "sv");
	l_dbus_message_builder_append_basic(builder, 's', "NetKeys");
	l_dbus_message_builder_enter_variant(builder, "a(qaya{sv})");
	l_dbus_message_builder_enter_array(builder, "(qaya{sv})");

	while ((entry = readdir(key_dir)) != NULL) {
		struct keyring_net_key key;
		int fd = open_key_dir_entry(key_dir_fd, entry, 3);

		if (fd < 0)
			continue;

		if (read(fd, &key, sizeof(key)) != sizeof(key)) {
			close(fd);
			goto done;
		}

		close(fd);

		/*
		 * If network key is stuck in phase 3, keyring
		 * write failed and this key info is unreliable.
		 */
		if (key.phase == KEY_REFRESH_PHASE_THREE)
			continue;

		l_dbus_message_builder_enter_struct(builder, "qaya{sv}");

		l_dbus_message_builder_append_basic(builder, 'q', &key.net_idx);
		dbus_append_byte_array(builder, key.new_key, 16);

		l_dbus_message_builder_enter_array(builder, "{sv}");

		if (key.phase != KEY_REFRESH_PHASE_NONE) {
			dbus_append_dict_entry_basic(builder, "Phase", "y",
								&key.phase);
			append_old_key(builder, key.old_key);
		}

		build_app_keys_reply(node_path, builder, key.net_idx,
								key.phase);

		l_dbus_message_builder_leave_array(builder);
		l_dbus_message_builder_leave_struct(builder);
	}

	l_dbus_message_builder_leave_array(builder);
	l_dbus_message_builder_leave_variant(builder);
	l_dbus_message_builder_leave_dict(builder);

	result = true;
done:
	closedir(key_dir);

	return result;

}

struct dev_key_entry {
	uint16_t unicast;
	uint8_t value[16];
};

static bool match_key_value(const void *a, const void *b)
{
	const struct dev_key_entry *key = a;
	const uint8_t *value = b;

	return (memcmp(key->value, value, 16) == 0);
}

static void build_dev_key_entry(void *a, void *b)
{
	struct dev_key_entry *key = a;
	struct l_dbus_message_builder *builder = b;

	l_dbus_message_builder_enter_struct(builder, "qay");
	l_dbus_message_builder_append_basic(builder, 'q', &key->unicast);
	dbus_append_byte_array(builder, key->value, 16);
	l_dbus_message_builder_leave_struct(builder);
}

static bool build_dev_keys_reply(const char *node_path,
					struct l_dbus_message_builder *builder)
{
	DIR *key_dir;
	int key_dir_fd;
	struct dirent *entry;
	struct l_queue *keys;
	bool result = false;

	key_dir = open_key_dir(node_path, dev_key_dir);
	/*
	 * There is always at least one device key present for a local node.
	 * Therefore, return false, if the directory does not exist.
	 */
	if (!key_dir)
		return false;

	key_dir_fd = dirfd(key_dir);

	keys = l_queue_new();

	while ((entry = readdir(key_dir)) != NULL) {
		uint8_t buf[16];
		uint16_t unicast;
		struct dev_key_entry *key;
		int fd = open_key_dir_entry(key_dir_fd, entry, 4);

		if (fd < 0)
			continue;

		if (read(fd, buf, 16) != 16) {
			close(fd);
			goto done;
		}

		close(fd);

		if (sscanf(entry->d_name, "%04hx", &unicast) != 1)
			goto done;

		key = l_queue_find(keys, match_key_value, buf);

		if (key) {
			if (key->unicast > unicast)
				key->unicast = unicast;
			continue;
		}

		key = l_new(struct dev_key_entry, 1);
		key->unicast = unicast;
		memcpy(key->value, buf, 16);
		l_queue_push_tail(keys, key);
	}

	l_dbus_message_builder_enter_dict(builder, "sv");
	l_dbus_message_builder_append_basic(builder, 's', "DevKeys");
	l_dbus_message_builder_enter_variant(builder, "a(qay)");
	l_dbus_message_builder_enter_array(builder, "(qay)");

	l_queue_foreach(keys, build_dev_key_entry, builder);

	l_dbus_message_builder_leave_array(builder);
	l_dbus_message_builder_leave_variant(builder);
	l_dbus_message_builder_leave_dict(builder);

	result = true;
done:
	l_queue_destroy(keys, l_free);
	closedir(key_dir);

	return result;
}

bool keyring_build_export_keys_reply(struct mesh_node *node,
					struct l_dbus_message_builder *builder)
{
	const char *node_path;

	if (!node)
		return false;

	node_path = node_get_storage_dir(node);

	if (!build_net_keys_reply(node_path, builder))
		return false;

	return build_dev_keys_reply(node_path, builder);
}
