/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * swtpm_localca_utils.c: Utility functions
 *
 * Author: Stefan Berger, stefanb@linux.ibm.com
 *
 * Copyright (c) IBM Corporation, 2021
 */

#include "config.h"

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <regex.h>
#include <errno.h>
#include <sys/file.h>

#include "swtpm_utils.h"
#include "swtpm_localca.h"
#include "swtpm_localca_utils.h"

/* Create a directory pat (and all its predecessors) if it doesn't exist */
int makedir(const char *dirname, const char *purpose)
{
    struct stat statbuf;

    if (stat(dirname, &statbuf) != 0) {
        logit(gl_LOGFILE, "Creating swtpm-localca dir '%s'.\n", dirname);
        if (g_mkdir_with_parents(dirname, S_IRWXU | S_IRWXG | S_IXGRP | S_IRGRP) == -1) {
            logerr(gl_LOGFILE, "Could not create directory for '%s': %s\n",
                   purpose, strerror(errno));
            return 1;
        }
    }
    return 0;
}

/* Get a configuration value given its name */
gchar *get_config_value(gchar **config_file_lines, const gchar *configname, const gchar *fallback)
{
    g_autofree gchar *regex = g_strdup_printf("^%s[[:space:]]*=[[:space:]]*([^#\n]*).*", configname);
    gchar *result = NULL;
    regex_t preg;
    size_t idx;
    regmatch_t pmatch[2];

    if (regcomp(&preg, regex, REG_EXTENDED) != 0) {
        logerr(gl_LOGFILE, "Internal error: Could not compile regex\n");
        goto error;
    }

    for (idx = 0; config_file_lines[idx] != NULL; idx++) {
        const gchar *line = config_file_lines[idx];
        if (regexec(&preg, line, 2, pmatch, 0) == 0) {
            g_autofree gchar *tmp = NULL;

            tmp = g_strndup(&line[pmatch[1].rm_so],
                            pmatch[1].rm_eo - pmatch[1].rm_so);
            g_strchomp(tmp);
            result = resolve_string(tmp);
            break;
        }
    }
    regfree(&preg);

error:
    if (result == NULL)
        result = g_strdup(fallback);
    //printf("Found match for %s: |%s|\n", configname, result);

    return result;
}

/* Extract all environment variables from the config file and add them to
 * the given environment.
 * Environment variable lines must start with 'env:' and must not contain
 * trailing spaces or a comment starting with '#'
 */
int get_config_envvars(gchar **config_file_lines, gchar ***env)
{
    const char *regex = "^env:([a-zA-Z_][a-zA-Z_0-9]*)[[:space:]]*=[[:space:]]*([^\n]*)";
    regex_t preg;
    size_t idx;
    regmatch_t pmatch[3];

    if (regcomp(&preg, regex, REG_EXTENDED) != 0) {
        logerr(gl_LOGFILE, "Internal error: Could not compile regex\n");
        return 1;
    }

    for (idx = 0; config_file_lines[idx] != NULL; idx++) {
        const gchar *line = config_file_lines[idx];
        if (regexec(&preg, line, 3, pmatch, 0) == 0) {
            g_autofree gchar *key = NULL, *value = NULL;

            key = g_strndup(&line[pmatch[1].rm_so],
                            pmatch[1].rm_eo - pmatch[1].rm_so);
            value = g_strndup(&line[pmatch[2].rm_so],
                              pmatch[2].rm_eo - pmatch[2].rm_so);
            *env = g_environ_setenv(*env, key, value, TRUE);
        }
    }

    regfree(&preg);

    return 0;
}

/* flock a file; the file descriptor for the file to unlock later on is returned */
int lock_file(const gchar *lockfile)
{
    int lockfd;
    mode_t mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;

    lockfd = open(lockfile, O_RDWR | O_CREAT, mode);
    if (lockfd < 0) {
        logerr(gl_LOGFILE, "Could not open lockfile %s: %s\n", lockfile, strerror(errno));
        return -1;
    }

    if (flock(lockfd, LOCK_EX) < 0) {
        logerr(gl_LOGFILE, "Could not lock file %s: %s\n", lockfile, strerror(errno));
        close(lockfd);
        return -1;
    }
    return lockfd;
}

/* unlock a file previously locked using lock_file */
void unlock_file(int lockfd) {
    if (lockfd >= 0) {
        flock(lockfd, LOCK_UN);
        close(lockfd);
    }
}

/* Replace a few characters in vmid so it can be used by CommonName in cert */
void vmid_replacechars(char *vmid) {
    size_t i = 0;
    char c;

    while ((c = vmid[i])) {
        switch (c) {
        case '+':
            // https://github.com/gnutls/gnutls/blob/gnutls_3_6_x/lib/x509/x509_dn.c#L167
            if (i == 0 || vmid[i - 1] != '\\')
                vmid[i] = '_';
            break;
        case ',':
            // no commas allowed
            vmid[i] = '_';
            break;
        }
        i++;
    }
}
