/*
 * Copyright (C) 2026 iopsys Software Solutions AB
 *
 * See LICENSE file for license related information.
 */

#include "cabundle.h"
#include "common.h"

#include <unistd.h>

/* Browse iterates the "Security" dmmap CABundle sections and resolves each
 * back to its `config cabundle` section in /etc/config/secmngr via
 * __section_name__. Instances come from the persisted dmmap __instance__ so
 * they are stable across reboots. The dmmap is (re)generated by
 * secmngr_dmmap_rebuild(); see common.c.
 */
int browseSecurityCABundleInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	struct dm_data curr_data = {0};
	char *inst = NULL;

	uci_path_foreach_sections(bbfdm, "Security", "CABundle", curr_data.dmmap_section) {
		char *config_sec_name = NULL, *path = NULL;

		dmuci_get_value_by_section_string(curr_data.dmmap_section, "__section_name__", &config_sec_name);
		curr_data.config_section = get_config_section_from_dmmap_section_name(config_sec_name);

		dmuci_get_value_by_section_string(curr_data.dmmap_section, "__path__", &path);

		/* Skip a config-backed row whose backing config section was removed
		 * out-of-band (before the next sync prunes it). The system bundle row
		 * (cert.pem) has no config section but carries a `path` — keep it. */
		if (curr_data.config_section == NULL && DM_STRLEN(path) == 0)
			continue;

		inst = uci_handle_instance(dmctx, parent_node, &curr_data);
		if (DM_LINK_INST_OBJ(dmctx, parent_node, &curr_data, inst) == DM_STOP)
			break;
	}
	return 0;
}

int get_Security_CABundleNumberOfEntries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = get_number_of_entries(ctx, data, instance, browseSecurityCABundleInst);
	dmasprintf(value, "%d", cnt);
	return 0;
}

/*************************************************************
 * PARAM HANDLERS
 *************************************************************/
/* Cached on the dmmap row at sync/Add time (see sync_cabundle_dmmap() and
 * operate_Security_AddCABundle() in common.c/sec_plugin.c) — this bundle's
 * own `config cabundle` section's `name` option (not its raw UCI section
 * identifier, which is prefixed "ca_<name>" — see
 * SECMNGR_CABUNDLE_SECTION_PREFIX in common.h — so it can't collide with a
 * `certificate` section of the same base Name), or (for the system bundle,
 * which has no backing section) the file's own basename with its extension
 * stripped. Read from there, not computed live, so it matches exactly what
 * bbfdm_get_reference_linker() resolves for this table's DM_FLAG_LINKER
 * parameter. */
static int get_SecurityCABundle_Name(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	char *v = NULL;

	dmuci_get_value_by_section_string(((struct dm_data *)data)->dmmap_section, "Name", &v);
	*value = dmstrdup(DM_STRLEN(v) ? v : "");
	return 0;
}

static int get_SecurityCABundle_CACertificates(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	struct uci_section *s = ((struct dm_data *)data)->config_section;
	struct uci_list *l = NULL;

	/* System bundle (cert.pem == concatenation of /etc/ssl/certs), read-only:
	 * synthesize the list by referencing every Certificate row derived from
	 * the system trust store — there is no persisted `cert` list to read
	 * for it, so membership is discovered by path prefix. Every such row
	 * has a cached Name (see secmngr_sync_certificate_dmmap_row() in
	 * common.c) same as any other certificate, so resolution goes through
	 * the same bbfdm_get_references() helper as the user-added-bundle
	 * branch below, rather than hand-building the pathRef from `__instance__`
	 * directly. */
	if (!s) {
		struct uci_section *cs = NULL;
		// cppcheck-suppress threadsafety-threadsafety
		static char buf[16384];
		size_t sys_len = strlen(SECMNGR_SYSTEM_CERT_PATH);

		buf[0] = '\0';
		uci_path_foreach_sections(bbfdm, "Security", "Certificate", cs) {
			char *p = NULL, *name = NULL;

			dmuci_get_value_by_section_string(cs, "__path__", &p);
			if (DM_STRLEN(p) == 0 ||
			    strncmp(p, SECMNGR_SYSTEM_CERT_PATH "/", sys_len + 1) != 0)
				continue;

			dmuci_get_value_by_section_string(cs, "Name", &name);
			if (DM_STRLEN(name) == 0)
				continue;

			bbfdm_get_references(ctx, MATCH_ALL, "Device.Security.Certificate.", "Name", name, buf, sizeof(buf));
		}
		*value = dmstrdup(buf);
		return 0;
	}

	/* User-added bundle: membership is stored as a `cert` list of each
	 * member's own Name (Device.Security.Certificate.{i}.Name is this
	 * table's DM_FLAG_LINKER parameter). Resolve each to its pathRef via the
	 * framework's generic reference helper — an entry that no longer
	 * resolves (e.g. the member cert was deleted independently via
	 * Certificate.{i}.Delete()) contributes nothing and is silently
	 * skipped, same as bbfdm's own Users/Group participation lists. */
	// cppcheck-suppress threadsafety-threadsafety
	static char buf[16384];

	buf[0] = '\0';
	dmuci_get_value_by_section_list(s, "cert", &l);
	if (l) {
		struct uci_element *e;

		uci_foreach_element(l, e) {
			bbfdm_get_references(ctx, MATCH_ALL, "Device.Security.Certificate.", "Name", e->name, buf, sizeof(buf));
		}
	}
	*value = dmstrdup(buf);
	return 0;
}

static int set_SecurityCABundle_CACertificates(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	struct uci_section *s = ((struct dm_data *)data)->config_section;
	struct dm_reference reference = {0};
	char **arr;
	size_t length;
	char *allowed_obj[] = { "Device.Security.Certificate.", NULL };

	switch (action) {
	case VALUECHECK:
		if (bbfdm_validate_string_list(ctx, value, -1, -1, -1, -1, -1, NULL, NULL))
			return FAULT_9007;

		if (DM_STRLEN(value) == 0)
			break;

		arr = strsplit(value, ",", &length);
		for (size_t i = 0; i < length; i++) {
			memset(&reference, 0, sizeof(reference));
			bbfdm_get_reference_linker(ctx, arr[i], &reference);

			if (dm_validate_allowed_objects(ctx, &reference, allowed_obj))
				return FAULT_9007;
		}
		break;
	case VALUESET:
		if (!s)
			break;

		dmuci_delete_by_section(s, "cert", NULL);

		if (DM_STRLEN(value) == 0)
			break;

		arr = strsplit(value, ",", &length);
		for (size_t i = 0; i < length; i++) {
			memset(&reference, 0, sizeof(reference));
			bbfdm_get_reference_linker(ctx, arr[i], &reference);

			if (DM_STRLEN(reference.value) == 0)
				continue;

			dmuci_add_list_value_by_section(s, "cert", reference.value);
		}
		break;
	}
	return 0;
}

/*************************************************************
 * OPERATE: Delete()
 *
 * TR-181 2.21 row-level command. Equivalent to USP RemoveObject on
 * this instance: remove the UCI/dmmap section and delete the persisted
 * combined bundle file. Member certificates are deliberately left
 * untouched — each is an independent Device.Security.Certificate.{i}. row
 * that can only be removed via its own Delete().
 *************************************************************/
static int operate_SecurityCABundle_Delete(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	struct uci_section *s = ((struct dm_data *)data)->config_section;
	char bundle_path[SECMNGR_CERT_PATH_LEN];
	char *name = NULL;

	(void)refparam; (void)ctx; (void)instance; (void)value; (void)action;

	if (!s)
		return 0;

	/* Name is the section's `name` option (set by AddCABundle()), not its
	 * raw UCI identifier — the section is named
	 * SECMNGR_CABUNDLE_SECTION_PREFIX + Name (see AddCABundle(),
	 * sec_plugin.c) to avoid colliding with a `certificate` section of the
	 * same base Name. */
	dmuci_get_value_by_section_string(s, "name", &name);
	if (DM_STRLEN(name)) {
		/* The persisted combined-bundle record from AddCABundle(). */
		if (snprintf(bundle_path, sizeof(bundle_path), "%s/%s.pem", SECMNGR_OWN_CABUNDLE_DIR, name) < (int)sizeof(bundle_path))
			unlink(bundle_path);
	}

	dmuci_delete_by_section(s, NULL, NULL);
	if (((struct dm_data *)data)->dmmap_section)
		dmuci_delete_by_section(((struct dm_data *)data)->dmmap_section, NULL, NULL);
	return 0;
}

/**********************************************************************************************************************************
 *                                            OBJ & PARAM DEFINITION
 **********************************************************************************************************************************/
/* Device.Security.CABundle.{i}. */
DMLEAF tSecurityCABundleParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, flags */
{"Name", &DMREAD, DMT_STRING, get_SecurityCABundle_Name, NULL, BBFDM_BOTH, DM_FLAG_UNIQUE|DM_FLAG_LINKER},
{"CACertificates", &DMWRITE, DMT_STRING, get_SecurityCABundle_CACertificates, set_SecurityCABundle_CACertificates, BBFDM_BOTH, DM_FLAG_REFERENCE},
{"Delete()", &DMSYNC, DMT_COMMAND, NULL, operate_SecurityCABundle_Delete, BBFDM_USP},
{0}
};
