/*
 * Copyright (C) 2026 iopsys Software Solutions AB
 *
 * Adapted from bbfdm/libbbfdm/security.c
 *   Original author: Omar Kallel <omar.kallel@pivasoftware.com>
 *
 * See LICENSE file for license related information.
 */

#include "certificate.h"
#include "common.h"

#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

/*************************************************************
 * ENTRY METHOD
 *************************************************************/
/* Browse iterates the "Security" dmmap Certificate sections. Each carries the
 * cert file `path` plus the readonly X509 fields cached at sync time (see
 * store_cert_fields() in common.c), so browse no longer parses the file — the
 * readonly getters read the cached values straight from the dmmap row.
 * 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 browseSecurityCertificateInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	struct certificate_profile certprofile = {0};
	struct dm_data curr_data = {0};
	char *inst = NULL;

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

		dmuci_get_value_by_section_string(curr_data.dmmap_section, "__path__", &path);
		if (DM_STRLEN(path) == 0)
			continue;

		/* Skip gracefully if the file vanished out-of-band before the next
		 * sync prunes its dmmap row — don't list a cert whose file is gone. */
		if (!file_exists(path))
			continue;

		/* Readonly X509 fields are served from the dmmap cache; only the path
		 * is needed at runtime (LastModif/Alias/Name/GetCertificateURI). */
		certprofile.path = path;
		certprofile.cert = NULL;
		curr_data.additional_data = (void *)&certprofile;

		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);

		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;
}

/* Serve a cached readonly field straight from this instance's dmmap row. */
static int get_cached_cert_field(void *data, const char *option, char **value)
{
	char *v = NULL;

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

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

/*************************************************************
 * GET / SET PARAM
 *************************************************************/
/* Stored on the "Security"/Certificate dmmap row itself (like usermngr's
 * get_user_alias()/set_user_alias()), not the per-cert `config certificate`
 * section — every certificate always has a dmmap row (system-store and
 * data-model-added alike), so this works uniformly with no special-casing
 * for certs that lack a config section. */
static int get_SecurityCertificate_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return bbf_get_alias(ctx, ((struct dm_data *)data)->dmmap_section, "Alias", instance, value);
}

static int set_SecurityCertificate_Alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	return bbf_set_alias(ctx, ((struct dm_data *)data)->dmmap_section, "Alias", instance, value);
}

/* Cached at sync/Add time (see secmngr_sync_certificate_dmmap_row() in
 * common.c): the config section's explicit Name if AddCertificateBundle()
 * set one, else the file's own basename with its extension stripped
 * (secmngr_basename_no_ext()) — every certificate always has a Name now,
 * never empty. */
static int get_SecurityCertificate_Name(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_cached_cert_field(data, "Name", value);
}

static int get_SecurityCertificate_LastModif(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	struct certificate_profile *cp = (struct certificate_profile *)((struct dm_data *)data)->additional_data;
	char buf[sizeof("0001-01-01T00:00:00Z")] = {0};
	struct stat b;

	if (!stat(cp->path, &b))
		strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&b.st_mtime));
	else
		DM_STRNCPY(buf, "0001-01-01T00:00:00Z", sizeof(buf));

	*value = dmstrdup(buf);
	return 0;
}

static int get_SecurityCertificate_SerialNumber(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_cached_cert_field(data, "SerialNumber", value);
}

static int get_SecurityCertificate_Issuer(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_cached_cert_field(data, "Issuer", value);
}

static int get_SecurityCertificate_NotBefore(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_cached_cert_field(data, "NotBefore", value);
}

static int get_SecurityCertificate_NotAfter(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_cached_cert_field(data, "NotAfter", value);
}

static int get_SecurityCertificate_Subject(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_cached_cert_field(data, "Subject", value);
}

/* Subject Alternative Names, rendered as a TR-181 <<list>> at sync time
 * (see format_subject_alt() in common.c) and served from the dmmap cache. */
static int get_SecurityCertificate_SubjectAlt(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_cached_cert_field(data, "SubjectAlt", value);
}

static int get_SecurityCertificate_SignatureAlgorithm(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	return get_cached_cert_field(data, "SignatureAlgorithm", value);
}

/*************************************************************
 * OPERATE: Delete()
 *
 * Per TR-181 2.21: removes the certificate from the device. We unlink
 * the underlying PEM file and drop any UCI tracking section.
 *
 * System-store certificates (path under SECMNGR_SYSTEM_CERT_PATH,
 * i.e. /etc/ssl/certs) are silently dropped (return success, do nothing)
 * rather than relying on filesystem permissions to stop the unlink — the
 * daemon commonly runs privileged enough to write there, so without this
 * guard Delete() would physically remove a system trust anchor and could
 * break TLS trust device-wide (ACS/USP, curl/wget, other daemons) for
 * every consumer of the system trust store, not just secmngr. Only
 * secmngr-owned certs (added via AddCertificateBundle()/AddCABundle(),
 * living under SECMNGR_OWN_CERT_DIR) are actually deleted this way.
 *************************************************************/
static int operate_SecurityCertificate_Delete(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	struct certificate_profile *cp = (struct certificate_profile *)((struct dm_data *)data)->additional_data;
	struct uci_section *dmmap_section = ((struct dm_data *)data)->dmmap_section;
	struct uci_section *s;
	char *key_path = NULL;
	char *cert_name = NULL;

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

	if (!cp || DM_STRLEN(cp->path) == 0)
		return USP_FAULT_COMMAND_FAILURE;

	if (strncmp(cp->path, SECMNGR_SYSTEM_CERT_PATH "/", strlen(SECMNGR_SYSTEM_CERT_PATH) + 1) == 0)
		return 0;

	s = secmngr_find_cert_section(cp->path);
	if (s)
		dmuci_get_value_by_section_string(s, "key_path", &key_path);

	if (unlink(cp->path) != 0 && errno != ENOENT)
		return USP_FAULT_COMMAND_FAILURE;

	if (DM_STRLEN(key_path))
		unlink(key_path);

	if (s)
		dmuci_delete_by_section(s, NULL, NULL);

	/* Name (this table's DM_FLAG_LINKER parameter) is what every
	 * CABundle.{i}.'s `cert` list stores per member — captured here
	 * before dropping the dmmap row, so any bundle still referencing
	 * this now-deleted certificate gets that stale entry cleaned up
	 * too, instead of CACertificates silently carrying a reference
	 * that no longer resolves to anything. */
	if (dmmap_section) {
		dmuci_get_value_by_section_string(dmmap_section, "Name", &cert_name);
		dmuci_delete_by_section(dmmap_section, NULL, NULL);
	}

	if (DM_STRLEN(cert_name)) {
		struct uci_section *cb = NULL;

		uci_foreach_sections(SECMNGR_UCI_PACKAGE, "cabundle", cb) {
			dmuci_del_list_value_by_section(cb, "cert", cert_name);
		}
	}

	return 0;
}

/*************************************************************
 * OPERATE: GetCertificateURI()
 *
 * TR-181 USP-only command. Returns a file:// URI for the cert, and a
 * file:// URI for the private key when one is tracked (certs added via
 * AddCertificateBundle()); PrivateKeyURI is empty for certs scanned from
 * /etc/ssl/certs. No HSM/PKCS#11 binding yet — see docs/softhsm_integration_proposal.md.
 *************************************************************/
static operation_args getcertificateuri_args = {
	.in  = (const char *[]) { NULL },
	.out = (const char *[]) { "CertificateURI", "PrivateKeyURI", NULL }
};

static int get_operate_args_SecurityCertificate_GetCertificateURI(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	*value = (char *)&getcertificateuri_args;
	return 0;
}

static int operate_SecurityCertificate_GetCertificateURI(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
	struct certificate_profile *cp = (struct certificate_profile *)((struct dm_data *)data)->additional_data;
	struct uci_section *s = secmngr_find_cert_section(cp->path);
	char cert_uri[SECMNGR_CERT_PATH_LEN + 8];
	char key_uri[SECMNGR_CERT_PATH_LEN + 8] = {0};
	char *key_path = NULL;

	(void)refparam; (void)value; (void)action;

	snprintf(cert_uri, sizeof(cert_uri), "file://%s", cp->path ? cp->path : "");

	/* If this cert was installed via AddCertificateBundle(), the UCI section
	 * records the on-disk path of the matching private key. Certs scanned
	 * from /etc/ssl/certs have no key tracked and PrivateKeyURI stays empty. */
	if (s)
		dmuci_get_value_by_section_string(s, "key_path", &key_path);
	if (DM_STRLEN(key_path))
		snprintf(key_uri, sizeof(key_uri), "file://%s", key_path);

	fill_blob_param(&ctx->bb, "CertificateURI", cert_uri, DMT_TYPE[DMT_STRING], 0);
	fill_blob_param(&ctx->bb, "PrivateKeyURI", key_uri, DMT_TYPE[DMT_STRING], 0);
	return 0;
}

/**********************************************************************************************************************************
 *                                            OBJ & PARAM DEFINITION
 **********************************************************************************************************************************/
/* Device.Security.Certificate.{i}. */
DMLEAF tSecurityCertificateParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, flags */
{"Alias", &DMWRITE, DMT_STRING, get_SecurityCertificate_Alias, set_SecurityCertificate_Alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Name", &DMREAD, DMT_STRING, get_SecurityCertificate_Name, NULL, BBFDM_BOTH, DM_FLAG_UNIQUE|DM_FLAG_LINKER},
{"LastModif", &DMREAD, DMT_TIME, get_SecurityCertificate_LastModif, NULL, BBFDM_BOTH},
{"SerialNumber", &DMREAD, DMT_STRING, get_SecurityCertificate_SerialNumber, NULL, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Issuer", &DMREAD, DMT_STRING, get_SecurityCertificate_Issuer, NULL, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"NotBefore", &DMREAD, DMT_TIME, get_SecurityCertificate_NotBefore, NULL, BBFDM_BOTH},
{"NotAfter", &DMREAD, DMT_TIME, get_SecurityCertificate_NotAfter, NULL, BBFDM_BOTH},
{"Subject", &DMREAD, DMT_STRING, get_SecurityCertificate_Subject, NULL, BBFDM_BOTH},
{"SubjectAlt", &DMREAD, DMT_STRING, get_SecurityCertificate_SubjectAlt, NULL, BBFDM_BOTH},
{"SignatureAlgorithm", &DMREAD, DMT_STRING, get_SecurityCertificate_SignatureAlgorithm, NULL, BBFDM_BOTH},
{"GetCertificateURI()", &DMSYNC, DMT_COMMAND, get_operate_args_SecurityCertificate_GetCertificateURI, operate_SecurityCertificate_GetCertificateURI, BBFDM_USP},
{"Delete()", &DMSYNC, DMT_COMMAND, NULL, operate_SecurityCertificate_Delete, BBFDM_USP},
{0}
};
