/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * You can obtain a copy of the licence at @.^.^.LICENSE.
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * CDDL HEADER END
 *
 * Copyright (c) RISC OS Developments 2026. All rights reserved.
 * SPDX-License-Identifier: CDDL-1.0
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "kernel.h"
#include "swis.h"
#include <openssl/rand.h>

#include "keys.h"
#include "manifest.h"
#include "module.h"
#include "transport.h"

#define ERROR_NUMBER 0x80ff00
#define DEFAULT_MANIFEST "<Choices$Read>.LanMan98.TLSManager.Manifest"

static unsigned char *active_data;
static tlsmanager_manifest active_manifest;
static int active;
static size_t active_size;
static unsigned int accepted_count;
static char last_anchor[65] = "none";
static char last_result[160] = "No manifest loaded";
static _kernel_oserror command_error;

const tlsmanager_manifest *TLSManagerActiveManifest(void)
{
    return active ? &active_manifest : NULL;
}

const char *TLSManagerLastResult(void)
{
    return last_result;
}

void TLSManagerRecordAccept(const unsigned char fingerprint[32])
{
    static const char hex[] = "0123456789abcdef";
    unsigned int i;

    accepted_count++;
    for(i = 0; i < 32; i++)
    {
        last_anchor[i * 2] = hex[fingerprint[i] >> 4];
        last_anchor[i * 2 + 1] = hex[fingerprint[i] & 15];
    }
    last_anchor[64] = '\0';
}

static void deactivate_manifest(const char *reason)
{
    if(active_data)
    {
        memset(active_data, 0, active_size);
        free(active_data);
    }
    active_data = NULL;
    active_size = 0;
    active = 0;
    if(reason)
    {
        strncpy(last_result, reason, sizeof(last_result) - 1u);
        last_result[sizeof(last_result) - 1u] = '\0';
    }
}

static _kernel_oserror *make_error(const char *text)
{
    command_error.errnum = ERROR_NUMBER;
    strncpy(command_error.errmess, text, sizeof(command_error.errmess) - 1u);
    command_error.errmess[sizeof(command_error.errmess) - 1u] = '\0';
    return &command_error;
}

static tlsmanager_counter current_time(void)
{
    tlsmanager_counter value;
    time_t now;

    value.high = value.low = 0;
    now = time(NULL);
    if(now > 0) value.low = (unsigned int)now;
    return value;
}

static _kernel_oserror *load_manifest(const char *name, int quiet,
                                      int activate)
{
    FILE *file;
    long length;
    unsigned char *data;
    tlsmanager_manifest candidate;
    tlsmanager_counter minimum;
    tlsmanager_manifest_result result;

    file = fopen(name, "rb");
    if(!file)
    {
        if(quiet) return NULL;
        return make_error("TLSManager could not open the manifest");
    }
    if(fseek(file, 0, SEEK_END) != 0 || (length = ftell(file)) < 0 ||
       length > 16384 || fseek(file, 0, SEEK_SET) != 0)
    {
        fclose(file);
        return make_error("TLSManager manifest has an invalid size");
    }
    data = malloc((size_t)length);
    if(!data)
    {
        fclose(file);
        return make_error("TLSManager has insufficient memory");
    }
    if(fread(data, 1, (size_t)length, file) != (size_t)length)
    {
        free(data);
        fclose(file);
        return make_error("TLSManager could not read the manifest");
    }
    fclose(file);

    minimum.high = minimum.low = 0;
    if(active) minimum = active_manifest.sequence;
    result = TLSManagerManifestParse(data, (size_t)length, current_time(),
                 minimum, TLSManagerVerifySignature, NULL, &candidate);
    if(result != TLSMANAGER_MANIFEST_OK)
    {
        sprintf(last_result, "Manifest rejected: %.120s",
                TLSManagerManifestResultText(result));
        free(data);
        if(quiet) return NULL;
        return make_error(last_result);
    }

    if(activate)
    {
        deactivate_manifest(NULL);
        active_data = data;
        active_size = (size_t)length;
        active_manifest = candidate;
        active = 1;
        accepted_count = 0;
        strcpy(last_anchor, "none");
        sprintf(last_result, "Manifest %u:%u active with %u anchors",
                active_manifest.sequence.high, active_manifest.sequence.low,
                active_manifest.anchor_count);
    }
    else
    {
        sprintf(last_result, "Manifest %u:%u is valid with %u anchors",
                candidate.sequence.high, candidate.sequence.low,
                candidate.anchor_count);
        memset(data, 0, (size_t)length);
        free(data);
    }
    return NULL;
}

_kernel_oserror *tlsmanager_init(const char *tail, int podule, void *pw)
{
    _kernel_oserror *error;

    active_data = NULL;
    active_size = 0;
    active = 0;
    accepted_count = 0;
    strcpy(last_anchor, "none");
    error = TLSManagerTransportInit();
    if(error) return error;
    if(!TLSManagerHasProductionKey())
        strcpy(last_result, "Inactive: production signing key not installed");
    else load_manifest(DEFAULT_MANIFEST, 1, 1);
    (void)tail;
    (void)podule;
    (void)pw;
    return NULL;
}

_kernel_oserror *tlsmanager_final(int fatal, int podule, void *pw)
{
    TLSManagerTransportFinal();
    deactivate_manifest(NULL);
    (void)fatal;
    (void)podule;
    (void)pw;
    return NULL;
}

static void show_status(int wimp)
{
    char text[240];

    if(TLSManagerHasProductionKey() && active)
        sprintf(text, "TLSManager 0.07 Beta; %.70s; %.80s; %u TLS connections",
                TLSManagerOpenSSLVersion(), last_result,
                TLSManagerTransportCount());
    else if(TLSManagerHasProductionKey())
        sprintf(text, "TLSManager 0.07 Beta; %.80s; %.90s",
                TLSManagerOpenSSLVersion(), last_result);
    else
        sprintf(text, "TLSManager 0.07 Beta; %.80s; CA %.40s; %u TLS connections",
                TLSManagerOpenSSLVersion(), TLSManagerCABundleVersion(),
                TLSManagerTransportCount());
    if(wimp)
    {
        _kernel_oserror message;
        message.errnum = 0;
        strncpy(message.errmess, text, sizeof(message.errmess) - 1u);
        message.errmess[sizeof(message.errmess) - 1u] = '\0';
        _swix(Wimp_ReportError, _INR(0,2), &message, 1, "TLSManager");
    }
    else printf("%s\n", text);
}

static _kernel_oserror *copy_info(void *buffer, int size, int *result)
{
    char text[220];
    int required;

    sprintf(text, "ABI 1; TLSManager 0.07 Beta; %.100s; CA %.40s",
            TLSManagerOpenSSLVersion(), TLSManagerCABundleVersion());
    required = (int)strlen(text) + 1;
    if(!buffer || size < required)
    {
        *result = -required;
        return NULL;
    }
    memcpy(buffer, text, (size_t)required);
    *result = required;
    return NULL;
}

_kernel_oserror *tlsmanager_swi(int swi, _kernel_swi_regs *registers,
                                void *pw)
{
    _kernel_oserror *error;
    unsigned int handle;
    size_t used;
    int status;
    int socket_number;
    int result;

    (void)pw;
    error = NULL;
    switch(swi)
    {
        case 0:
            if(registers->r[0] != 0) return make_error("TLSManager flags must be zero");
            error = copy_info((void *)registers->r[1], registers->r[2], &result);
            registers->r[2] = result;
            break;
        case 1:
            if(registers->r[0] != 0) return make_error("TLSManager flags must be zero");
            error = TLSManagerTransportOpen((const tlsmanager_open_block *)registers->r[1],
                                             (size_t)registers->r[2], &handle);
            if(!error) registers->r[0] = (int)handle;
            break;
        case 2:
            if(registers->r[0] != 0) return make_error("TLSManager flags must be zero");
            error = TLSManagerTransportStep((unsigned int)registers->r[1],
                                             &status, &socket_number);
            if(!error)
            {
                registers->r[0] = status;
                registers->r[1] = socket_number;
            }
            break;
        case 3:
            if(registers->r[0] != 0) return make_error("TLSManager flags must be zero");
            error = TLSManagerTransportRead((unsigned int)registers->r[1],
                        (void *)registers->r[2], (size_t)registers->r[3],
                        &used, &status);
            if(!error)
            {
                registers->r[0] = (int)used;
                registers->r[1] = status;
            }
            break;
        case 4:
            if(registers->r[0] != 0) return make_error("TLSManager flags must be zero");
            error = TLSManagerTransportWrite((unsigned int)registers->r[1],
                        (const void *)registers->r[2], (size_t)registers->r[3],
                        &used, &status);
            if(!error)
            {
                registers->r[0] = (int)used;
                registers->r[1] = status;
            }
            break;
        case 5:
            error = TLSManagerTransportClose((unsigned int)registers->r[1],
                                              (unsigned int)registers->r[0]);
            break;
        case 6:
            if(registers->r[0] == 0)
            {
                error = copy_info((void *)registers->r[1], registers->r[2], &result);
                registers->r[2] = result;
            }
            else if(registers->r[0] == 3)
                registers->r[0] = RAND_status() == 1 ? 0 : 1;
            else return make_error("TLSManager control reason is not implemented");
            break;
        default:
            return make_error("TLSManager SWI is not known");
    }
    return error;
}

_kernel_oserror *tlsmanager_commands(const char *arguments, int argc,
                                     int command, void *pw)
{
    char name[256];
    size_t length;

    (void)argc;
    (void)pw;
    if(command == 0)
    {
        show_status(argc == 1 && strcmp(arguments, "-wimp") == 0);
        return NULL;
    }
    if(command == 3)
    {
        deactivate_manifest("Inactive: policy deactivated by user");
        return NULL;
    }
    while(*arguments == ' ') arguments++;
    length = strlen(arguments);
    while(length && arguments[length - 1] == ' ') length--;
    if(length >= sizeof(name)) return make_error("TLSManager filename is too long");
    if(length >= 2 && arguments[0] == '"' && arguments[length - 1] == '"')
    {
        arguments++;
        length -= 2;
    }
    memcpy(name, arguments, length);
    name[length] = '\0';
    return load_manifest(name, 0, command == 1);
}
