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

#ifndef __SECMNGR_COMMON_H
#define __SECMNGR_COMMON_H

#include <libbbfdm-api/dmcommon.h>

#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>

#define SECMNGR_UCI_PACKAGE          "secmngr"
#define SECMNGR_SYSTEM_CERT_PATH     "/etc/ssl/certs"
#define SECMNGR_SYSTEM_CABUNDLE_PATH "/etc/ssl/cert.pem"
#define SECMNGR_DEFAULT_BASE_PATH  "/etc/secmngr"
#define SECMNGR_OWN_CERT_DIR       SECMNGR_DEFAULT_BASE_PATH "/certs"
#define SECMNGR_OWN_KEY_DIR        SECMNGR_DEFAULT_BASE_PATH "/private"
#define SECMNGR_OWN_CABUNDLE_DIR   SECMNGR_DEFAULT_BASE_PATH "/cabundles"
#define SECMNGR_CERT_PATH_LEN      512
#define SECMNGR_MAX_SECTION_NAME_LEN 64

/* UCI section names are unique per-package across *all* section types, not
 * per type — a `certificate` section and a `cabundle` section could
 * otherwise collide if both were named directly after a (spec-legal,
 * independently-unique-per-type) Certificate.Name/CABundle.Name that happen
 * to match. Every `config certificate`/`config cabundle` section this
 * plugin creates is named with one of these prefixes to keep the two types
 * in disjoint namespaces; apply them everywhere a section of that type is
 * named, renamed, or looked up by its UCI identifier. */
#define SECMNGR_CERT_SECTION_PREFIX     "cert_"
#define SECMNGR_CABUNDLE_SECTION_PREFIX "ca_"

struct certificate_profile {
	char *path;
	X509 *cert;
};

int secmngr_init(void *data);
int secmngr_clean(void *data);
int secmngr_sync(void *data);

/* Discover certificate files (system trust store plus SECMNGR_OWN_CERT_DIR,
 * subject to SECMNGR_SKIP_SYSTEM_CERTS) and invoke cb(cert_path, arg) once
 * per file found. No fixed cap on the number of certs discovered. */
void secmngr_discover_certificates(void (*cb)(const char *cert_path, void *arg), void *arg);

/* Find the UCI section in /etc/config/secmngr whose `path` option matches
 * the given absolute path. Returns NULL if none. */
struct uci_section *secmngr_find_cert_section(const char *cert_path);

/* Find the "Security"/Certificate dmmap row whose cached SerialNumber+Issuer
 * (the certificate's functional unique key per spec) match cert's, or NULL.
 * Used by AddCertificateBundle() (sec_plugin.c) to detect "this exact
 * certificate is already tracked" so it can replace that entry in place
 * rather than deriving a fresh (potentially colliding) path. */
struct uci_section *secmngr_find_certificate_dmmap_by_cert(X509 *cert);

/* Ensure a `config certificate` section exists for the given path, named
 * SECMNGR_CERT_SECTION_PREFIX + name (e.g. "cert_foo") so it can't collide
 * with a `config cabundle` section of the same base Name. If a section for
 * cert_path already exists under a different name (its Name changed), it is
 * renamed in place; its `name` option is set/updated to name either way.
 * name must be non-empty. Returns the section (or NULL on failure). */
struct uci_section *secmngr_ensure_cert_section(const char *cert_path, const char *name);

/* Derive a filesystem-safe, UCI-section-name-safe stem
 * ("<serial-hex>_<issuer-hash>") for a certificate's on-disk filename from
 * its SerialNumber+Issuer identity — see the .c file for why SerialNumber
 * alone isn't a safe filename key, and why '_' (not '-') separates the two
 * halves. Every place a certificate's on-disk filename, or (absent an
 * explicit Name) its Certificate.{i}.Name fallback, is derived from
 * identity must use this. */
void secmngr_cert_file_stem(X509 *cert, char *out, size_t outlen);

/* Return the basename of path with its final extension stripped (e.g.
 * "/etc/ssl/cert.pem" -> "cert", "/etc/ssl/certs/abc.crt" -> "abc"). Writes
 * into out (caller-allocated, at least outlen bytes); always non-empty for
 * any non-empty path. */
void secmngr_basename_no_ext(const char *path, char *out, size_t outlen);

/* Create (or find) the "Security"/Certificate dmmap row for cert_path and
 * (re)populate its cached X.509 fields + Name immediately, so the row is
 * fully resolvable and populated without waiting for the next
 * secmngr_sync() rebuild. Only *finds* an existing `config certificate`
 * section for cert_path — never creates one itself (callers that want a
 * real backing section, e.g. install_certs_from_bio(), call
 * secmngr_ensure_cert_section() first). Only a system-store cert (which
 * never gets a config section at all) still falls back to the file's own
 * basename for Name. *instance, if non-NULL, is set to the row's instance
 * number string. Returns the dmmap section, or NULL on failure. Must be
 * called with a live dmctx (e.g. from within an operate handler), matching
 * create_dmmap_obj()'s own requirement. */
struct uci_section *secmngr_sync_certificate_dmmap_row(struct dmctx *dmctx,
		const char *cert_path, char **instance);

/* Rebuild the "Security" dmmap (package /etc/bbfdm/dmmap/Security) from the
 * live secmngr config and the discovered certificate files: create missing
 * Certificate/CABundle sections and prune ones whose backing config section or
 * cert file has disappeared. Runs in its own bbf context and commits. Called
 * from secmngr_init() at load, secmngr_sync() on external UCI change, and after
 * the operate Add commands so the new instance is immediately resolvable. */
void secmngr_dmmap_rebuild(void);

/* Hex-decode an ASCII string. *out is calloc'd and the byte length is
 * returned, or -1 on malformed input. Caller frees with FREE(). */
int secmngr_hex_decode(const char *hex, unsigned char **out);

/*************************************************************
 * Bundle input decoding
 *
 * Device.Security.{AddCertificateBundle,AddCABundle}()'s `Bundle` input
 * argument stays spec-typed hexBinary externally. secmngr_decode_bundle_input()
 * additionally accepts the payload as plain text (e.g. a literal PEM blob)
 * for callers that would rather not hex-encode it themselves — a deliberate
 * accommodation beyond the spec's own contract, not a replacement for it.
 * Everything downstream (PEM parsing, cert/key extraction, fault codes) is
 * unchanged: this is purely about turning whatever the caller sent into the
 * raw bytes AddCertificateBundle()/AddCABundle() already know how to parse.
 *
 * This is the single entry point call sites
 * (operate_Security_AddCertificateBundle()/operate_Security_AddCABundle() in
 * sec_plugin.c) should use in place of a direct secmngr_hex_decode() call.
 * Supporting another input encoding later means adding one entry to the
 * format-handler table in common.c (see bundle_format_handlers) — no call
 * sites need to change.
 *************************************************************/

/* Decode a Bundle input value into raw bytes ready for PEM parsing. *out is
 * calloc'd (caller frees with FREE()). Returns the byte length, or -1 if
 * input is NULL, empty, or malformed for every known format. */
int secmngr_decode_bundle_input(const char *input, unsigned char **out);

/* Parse a PEM blob (one or more concatenated PEM-encoded X509 certs). For
 * each cert: write it to SECMNGR_OWN_CERT_DIR/<stem>.pem (stem from
 * secmngr_cert_file_stem()) and give it a real `config certificate`
 * section (named "cert_<stem>"); if that
 * section can't be created, this cert's dmmap row is skipped too (kept in
 * sync — no dmmap row without a backing config section) rather than left
 * unbacked, though the file itself is still picked up, and given a proper
 * row, the next time secmngr_sync() rescans SECMNGR_OWN_CERT_DIR. Otherwise
 * ensures a "Security"/Certificate dmmap row exists for it (deduped by
 * Issuer+SerialNumber — a cert already present, e.g. shared with another
 * bundle or discovered independently, is reused rather than duplicated: no
 * new file or section), and adds that row's cached Name directly to
 * bundle_section's `cert` list (a cert whose section/dmmap row couldn't be
 * created, or that collided on path with an unrelated file, is skipped and
 * does not get a list entry). No fixed cap on the number of certs in a
 * bundle. Must be called with a live dmctx. Returns the number of certs
 * processed, or negative on failure:
 *   -7265 if the blob isn't recognisable PEM
 *   -7262 if the blob couldn't even be wrapped in a BIO
 *   -7266 on disk-write failure */
int secmngr_install_cabundle_certs(struct dmctx *dmctx, const unsigned char *blob, int blen,
                                   struct uci_section *bundle_section);

#endif /* __SECMNGR_COMMON_H */
