/*
 * Ouroboros - Copyright (C) 2016 - 2026
 *
 * The IPC Resource Manager / Configuration from file
 *
 *    Dimitri Staessens <dimitri@ouroboros.rocks>
 *    Sander Vrijders   <sander@ouroboros.rocks>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., http://www.fsf.org/about/contact/.
 */


#include "config.h"

#if defined (HAVE_TOML)

#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE   500

#define OUROBOROS_PREFIX "irmd/configuration"

#include <ouroboros/errno.h>
#include <ouroboros/ipcp.h>
#include <ouroboros/logs.h>
#include <ouroboros/utils.h>

#include "irmd.h"
#include "configfile.h"

#include "reg/reg.h"

#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <toml.h>
#include <arpa/inet.h>
#ifdef __FreeBSD__
#include <sys/socket.h>
#endif

#define ERRBUFSZ 200
#define DATUMSZ  256

static int toml_hash(toml_table_t *      table,
                     struct layer_info * info)
{
        toml_datum_t hash;

        hash = toml_string_in(table, "hash");
        if (!hash.ok) {
                log_dbg("No hash specified, using default.");
                return 0;
        }

        if (strcmp(hash.u.s, "SHA3_224") == 0) {
                info->dir_hash_algo = DIR_HASH_SHA3_224;
        } else if (strcmp(hash.u.s, "SHA3_256") == 0) {
                info->dir_hash_algo = DIR_HASH_SHA3_256;
        } else if (strcmp(hash.u.s, "SHA3_384") == 0) {
                info->dir_hash_algo = DIR_HASH_SHA3_384;
        } else if (strcmp(hash.u.s, "SHA3_512") == 0) {
                info->dir_hash_algo = DIR_HASH_SHA3_512;
        } else {
                log_err("Unknown hash algorithm: %s.", hash.u.s);
                free(hash.u.s);
                return -1;
        }

        free(hash.u.s);

        return 0;
}

static int toml_local(toml_table_t *       table,
                      struct ipcp_config * conf)
{
        *conf = local_default_conf;

        return toml_hash(table, &conf->layer_info);
}

static int toml_eth_dev(toml_table_t *      table,
                        struct eth_config * conf)
{
        toml_datum_t dev;

        dev = toml_string_in(table, "dev");
        if (!dev.ok) {
                log_err("Missing device.");
                return -1;
        }

        if (strlen(dev.u.s) > DEV_NAME_SIZE) {
                log_err("Device name too long: %s", dev.u.s);
                free(dev.u.s);
                return -1;
        }

        strcpy(conf->dev, dev.u.s);
        free(dev.u.s);

        return 0;
}

static int toml_eth_llc(toml_table_t *       table,
                        struct ipcp_config * conf)
{
        *conf = eth_llc_default_conf;

        if (toml_hash(table, &conf->layer_info) < 0)
                return -1;

        return toml_eth_dev(table, &conf->eth);
}


static int toml_ethertype(toml_table_t *      table,
                          struct eth_config * conf)
{
        toml_datum_t ethertype;

        ethertype = toml_int_in(table, "ethertype");
        if (ethertype.ok)
                conf->ethertype = ethertype.u.i;

        if (conf->ethertype < 0x0600 || conf->ethertype == 0xFFFF)
                return -1;

        return 0;
}

static int toml_eth_dix(toml_table_t *       table,
                        struct ipcp_config * conf)
{
        *conf = eth_dix_default_conf;

        if (toml_hash(table, &conf->layer_info) < 0)
                return -1;

        if (toml_eth_dev(table, &conf->eth) < 0)
                return -1;

        if (toml_ethertype(table, &conf->eth) < 0) {
                log_err("Ethertype not in valid range.");
                return -1;
        }

        return 0;
}

static int toml_udp4(toml_table_t *       table,
                     struct ipcp_config * conf)
{
        struct udp4_config * udp4;
        toml_datum_t ip;
        toml_datum_t port;
        toml_datum_t dns;

        *conf = udp4_default_conf;
        udp4 = &conf->udp4;

        ip = toml_string_in(table, "ip");
        if (!ip.ok) {
                log_err("No IP address specified!");
                goto fail_ip;
        }

        if (inet_pton (AF_INET, ip.u.s, &udp4->ip_addr.s_addr) != 1) {
                log_err("Failed to parse IPv4 address %s.", ip.u.s);
                goto fail_addr;
        }

        port = toml_int_in(table, "port");
        if (port.ok)
                udp4->port = port.u.i;

        dns = toml_string_in(table, "dns");
        if (dns.ok) {
                if (inet_pton(AF_INET, dns.u.s, &udp4->dns_addr.s_addr) < 0) {
                        log_err("Failed to parse DNS address %s.", ip.u.s);
                        goto fail_dns;
                }

                free(dns.u.s);
        }

        free(ip.u.s);

        return 0;

 fail_dns:
        free(dns.u.s);
 fail_addr:
        free(ip.u.s);
 fail_ip:
        return -1;
}

static int toml_udp6(toml_table_t *       table,
                     struct ipcp_config * conf)
{
        struct in6_addr ip6;
        struct in6_addr dns6;
        toml_datum_t ip;
        toml_datum_t port;
        toml_datum_t dns;

        *conf = udp6_default_conf;
        ip6  = conf->udp6.ip_addr;
        dns6 = conf->udp6.dns_addr;

        ip = toml_string_in(table, "ip");
        if (!ip.ok) {
                log_err("No IP address specified!");
                goto fail_ip;
        }

        if (inet_pton (AF_INET6, ip.u.s, &ip6.s6_addr) != 1) {
                log_err("Failed to parse IPv4 address %s.", ip.u.s);
                goto fail_addr;
        }

        port = toml_int_in(table, "port");
        if (port.ok)
                conf->udp6.port = port.u.i;

        dns = toml_string_in(table, "dns");
        if (dns.ok) {
                if (inet_pton(AF_INET6, dns.u.s, &dns6.s6_addr) < 0) {
                        log_err("Failed to parse DNS address %s.", ip.u.s);
                        goto fail_dns;
                }

                free(dns.u.s);
        }

        free(ip.u.s);

        return 0;

 fail_dns:
        free(dns.u.s);
 fail_addr:
        free(ip.u.s);
 fail_ip:
        return -1;
}

static int toml_broadcast(toml_table_t *       table,
                          struct ipcp_config * conf)
{
        (void) table;
        (void) conf;

        /* Nothing to do here. */

        return 0;
}

#define BETWEEN(a, b, c) ((a) >= (b) && (a) <= (c))
#define DHT(conf, x) (conf)->dht.params.x
static int toml_dir(toml_table_t *      table,
                     struct dir_config * conf)
{
        toml_datum_t dir;
        toml_datum_t alpha;
        toml_datum_t t_expire;
        toml_datum_t t_refresh;
        toml_datum_t t_replicate;
        toml_datum_t k;

        dir = toml_string_in(table, "directory");
        if (dir.ok) {
                log_dbg("Found directory type: %s", dir.u.s);
                if (strlen(dir.u.s) > DATUMSZ) {
                        log_err("Directory name too long: %s", dir.u.s);
                        free(dir.u.s);
                        return -1;
                }
                if (strcmp(dir.u.s, "DHT") == 0)
                        conf->pol = DIR_DHT;
                else if (strcmp(dir.u.s, "dht") == 0)
                        conf->pol = DIR_DHT;
                else {
                        log_err("Unknown directory type: %s", dir.u.s);
                        free(dir.u.s);
                        return -EINVAL;
                }
                free(dir.u.s);
        }

        switch(conf->pol) {
        case DIR_DHT:
                log_info("Using DHT directory policy.");
                alpha = toml_int_in(table, "dht_alpha");
                if (alpha.ok) {
                        if (!BETWEEN(alpha.u.i,
                                DHT_ALPHA_MIN, DHT_ALPHA_MAX)) {
                                log_err("Invalid alpha value: %ld",
                                        (long) alpha.u.i);
                                return -EINVAL;
                        }
                        DHT(conf, alpha) = alpha.u.i;
                }
                t_expire = toml_int_in(table, "dht_t_expire");
                if (t_expire.ok) {
                        if (!BETWEEN(t_expire.u.i,
                                DHT_T_EXPIRE_MIN, DHT_T_EXPIRE_MAX)) {
                                log_err("Invalid expire time: %ld",
                                        (long) t_expire.u.i);
                                return -EINVAL;
                        }
                        DHT(conf, t_expire) = t_expire.u.i;
                }
                t_refresh = toml_int_in(table, "dht_t_refresh");
                if (t_refresh.ok) {
                        if (!BETWEEN(t_refresh.u.i,
                                DHT_T_REFRESH_MIN, DHT_T_REFRESH_MAX)) {
                                log_err("Invalid refresh time: %ld",
                                        (long) t_refresh.u.i);
                                return -EINVAL;
                        }
                        DHT(conf, t_refresh) = t_refresh.u.i;
                }
                t_replicate = toml_int_in(table, "dht_t_replicate");
                if (t_replicate.ok) {
                        if (!BETWEEN(t_replicate.u.i,
                                DHT_T_REPLICATE_MIN, DHT_T_REPLICATE_MAX)) {
                                log_err("Invalid replication time: %ld",
                                        (long) t_replicate.u.i);
                                return -EINVAL;
                        }
                        DHT(conf, t_replicate) = t_replicate.u.i;
                }
                k = toml_int_in(table, "dht_k");
                if (k.ok) {
                        if (!BETWEEN(k.u.i, DHT_K_MIN, DHT_K_MAX)) {
                                log_err("Invalid replication factor: %ld",
                                        (long) k.u.i);
                                return -EINVAL;
                        }
                        DHT(conf, k) = k.u.i;
                }
                break;
        default:
                assert(false);
                break;
        }

        return 0;
}

static int toml_routing(toml_table_t *     table,
                        struct dt_config * conf)
{
        toml_datum_t routing;
        toml_datum_t t_recalc;
        toml_datum_t t_update;
        toml_datum_t t_timeo;

        routing = toml_string_in(table, "routing");
        if (routing.ok) {
                if (strcmp(routing.u.s, "link-state") == 0) {
                        conf->routing.pol = ROUTING_LINK_STATE;
                        conf->routing.ls.pol = LS_SIMPLE;
                } else if (strcmp(routing.u.s, "lfa") == 0) {
                        conf->routing.pol = ROUTING_LINK_STATE;
                        conf->routing.ls.pol = LS_LFA;
                } else if (strcmp(routing.u.s, "ecmp") == 0) {
                        conf->routing.pol = ROUTING_LINK_STATE;
                        conf->routing.ls.pol = LS_ECMP;
                } else {
                        conf->routing.pol = ROUTING_INVALID;
                        return -EINVAL;
                }
                free(routing.u.s);
        }

        switch (conf->routing.pol) {
        case ROUTING_LINK_STATE:
                log_info("Using Link State routing policy.");
                t_recalc = toml_int_in(table, "ls_t_recalc");
                if (t_recalc.ok) {
                        if (t_recalc.u.i < 1) {
                                log_err("Invalid ls_t_recalc value: %ld",
                                        (long) t_recalc.u.i);
                                return -EINVAL;
                        }
                        conf->routing.ls.t_recalc = t_recalc.u.i;
                }
                t_update = toml_int_in(table, "ls_t_update");
                if (t_update.ok) {
                        if (t_update.u.i < 1) {
                                log_err("Invalid ls_t_update value: %ld",
                                        (long) t_update.u.i);
                                return -EINVAL;
                        }
                        conf->routing.ls.t_update = t_update.u.i;
                }
                t_timeo = toml_int_in(table, "ls_t_timeo");
                if (t_timeo.ok) {
                        if (t_timeo.u.i < 1) {
                                log_err("Invalid ls_t_timeo value: %ld",
                                        (long) t_timeo.u.i);
                                return -EINVAL;
                        }
                        conf->routing.ls.t_timeo = t_timeo.u.i;
                }
                break;
        default:
                log_err("Invalid routing policy: %d", conf->routing.pol);
                return -EINVAL;
        }

        return 0;
}

static int toml_addr_auth(toml_table_t *      table,
                          struct uni_config * conf)
{
        toml_datum_t addr_auth;

        addr_auth = toml_string_in(table, "addr-auth");
        if (addr_auth.ok) {
                if (strcmp(addr_auth.u.s, "flat") == 0)
                        conf->addr_auth_type = ADDR_AUTH_FLAT_RANDOM;
                else
                        conf->addr_auth_type = ADDR_AUTH_INVALID;
                free(addr_auth.u.s);
        }

        if (conf->addr_auth_type == ADDR_AUTH_INVALID)
                return -1;

        return 0;
}

static int toml_congestion(toml_table_t *      table,
                           struct uni_config * conf)
{
        toml_datum_t congestion;

        congestion = toml_string_in(table, "congestion");
        if (congestion.ok) {
                if (strcmp(congestion.u.s, "none") == 0)
                        conf->cong_avoid = CA_NONE;
                else if (strcmp(congestion.u.s, "lfa") == 0)
                        conf->cong_avoid = CA_MB_ECN;
                else
                        conf->cong_avoid = CA_INVALID;
                free(congestion.u.s);

        }

        if (conf->cong_avoid == CA_INVALID)
                return -1;

        return 0;
}

static int toml_dt(toml_table_t *     table,
                   struct dt_config * conf)
{
        toml_datum_t addr;
        toml_datum_t eid;
        toml_datum_t ttl;

        addr = toml_int_in(table, "addr_size");
        if (addr.ok)
                conf->addr_size = addr.u.i;

        eid = toml_int_in(table, "eid_size");
        if (eid.ok)
                conf->eid_size = eid.u.i;

        ttl = toml_int_in(table, "max_ttl");
        if (ttl.ok)
                conf->max_ttl = ttl.u.i;

        if (toml_routing(table, conf) < 0) {
                log_err("Invalid routing option.");
                return -1;
        }

        return 0;
}

static int toml_unicast(toml_table_t *       table,
                        struct ipcp_config * conf)
{
        *conf = uni_default_conf;

        if (toml_dir(table, &conf->unicast.dir) < 0) {
                log_err("Invalid directory configuration.");
                return -1;
        }

        if (toml_dt(table, &conf->unicast.dt) < 0) {
                log_err("Invalid DT configuration.");
                return -1;
        }

        if (toml_addr_auth(table, &conf->unicast) < 0) {
                log_err("Invalid address authority");
                return -1;
        }

        if (toml_congestion(table, &conf->unicast) < 0) {
                log_err("Invalid congestion avoidance algorithm.");
                return -1;
        }


        return 0;
}

static int toml_autobind(toml_table_t * table,
                         pid_t          pid,
                         const char *   name,
                         const char *   layer)
{
        toml_datum_t autobind;

        autobind = toml_bool_in(table, "autobind");
        if (!autobind.ok)
                return 0;

        if (bind_process(pid, name) < 0) {
                log_err("Failed to bind IPCP process %d to %s.", pid, name);
                return -1;
        }

        if (layer != NULL && bind_process(pid, layer) < 0) {
                log_err("Failed to bind IPCP process %d to %s.", pid, layer);
                return -1;
        }

        return 0;
}

static int toml_register(toml_table_t * table,
                         pid_t          pid)
{
        toml_array_t *   reg;
        int              i;
        int              ret = 0;
        struct name_info info = {
                .pol_lb = LB_SPILL
        };

        reg = toml_array_in(table, "reg");
        if (reg == NULL)
                return 0;

        for (i = 0; ret == 0; i++) {
                toml_datum_t name;

                name = toml_string_at(reg, i);
                if (!name.ok)
                        break;

                log_dbg("Registering %s in %d", name.u.s, pid);

                strcpy(info.name, name.u.s);

                ret = name_create(&info);
                if (ret < 0 && ret != -ENAME) {
                        free(name.u.s);
                        break;
                }

                ret = name_reg(name.u.s, pid);
                free(name.u.s);
        }

        return ret;
}

static int toml_connect(toml_table_t * table,
                        pid_t          pid)
{
        toml_array_t * conn;
        int            i;
        int            ret = 0;

        conn = toml_array_in(table, "conn");
        if (conn == NULL)
                return 0;

        for (i=0; ret == 0; i++) {
                toml_datum_t dst;
                qosspec_t    qs = qos_raw;

                dst = toml_string_at(conn, i);
                if (!dst.ok)
                        break;

                log_dbg("Connecting %d to %s", pid, dst.u.s);

                ret = connect_ipcp(pid, dst.u.s, MGMT_COMP, qs);
                if (ret == 0)
                        ret = connect_ipcp(pid, dst.u.s, DT_COMP, qs);

                free(dst.u.s);
        }

        return ret;
}

static int toml_ipcp(toml_table_t *       table,
                     struct ipcp_info *   info,
                     struct ipcp_config * conf)
{
        toml_datum_t bootstrap;
        toml_datum_t enrol;
        int          ret;

        log_dbg("Found IPCP %s in configuration file.", info->name);

        if (create_ipcp(info) < 0) {
                log_err("Failed to create IPCP %s.", info->name);
                return -1;
        }

        bootstrap = toml_string_in(table, "bootstrap");
        enrol     = toml_string_in(table, "enrol");

        if (bootstrap.ok && enrol.ok) {
                log_err("Ignoring bootstrap for IPCP %s.", info->name);
                free(bootstrap.u.s);
                bootstrap.ok = false;
        }

        if (!bootstrap.ok && !enrol.ok) {
                log_dbg("Nothing more to do for %s.", info->name);
                return 0;
        }

        if (enrol.ok) {
                struct layer_info layer;
                ret = enroll_ipcp(info->pid, enrol.u.s);
                free(enrol.u.s);
                if (ret < 0) {
                        log_err("Failed to enrol %s.", info->name);
                        return -1;
                }

                if (reg_get_ipcp(info, &layer) < 0)
                        return -1;

                if (toml_autobind(table, info->pid, info->name, layer.name))
                        return -1;

                if (toml_register(table, info->pid) < 0) {
                        log_err("Failed to register names.");
                        return -1;
                }

                if (toml_connect(table, info->pid) < 0) {
                        log_err("Failed to register names.");
                        return -1;
                }

                return 0;
        }

        assert(bootstrap.ok);

        if (strlen(bootstrap.u.s) > LAYER_NAME_SIZE) {
                log_err("Layer name too long: %s", bootstrap.u.s);
                free(bootstrap.u.s);
                return -1;
        }

        switch (conf->type) {
        case IPCP_LOCAL:
                ret = toml_local(table, conf);
                break;
        case IPCP_ETH_DIX:
                ret = toml_eth_dix(table, conf);
                break;
        case IPCP_ETH_LLC:
                ret = toml_eth_llc(table, conf);
                break;
        case IPCP_UDP4:
                ret = toml_udp4(table, conf);
                break;
        case IPCP_UDP6:
                ret = toml_udp6(table, conf);
                break;
        case IPCP_BROADCAST:
                ret = toml_broadcast(table, conf);
                break;
        case IPCP_UNICAST:
                ret = toml_unicast(table, conf);
                break;
        default:
                log_err("Invalid IPCP type");
                ret = -1;
        }

        if (ret < 0)
                return -1;

        strcpy(conf->layer_info.name, bootstrap.u.s);
        free(bootstrap.u.s);

        if (bootstrap_ipcp(info->pid, conf) < 0)
                return -1;

        if (toml_autobind(table, info->pid, info->name,
                          conf->layer_info.name) < 0)
                return -1;

        if (toml_register(table, info->pid) < 0) {
                log_err("Failed to register names.");
                return -1;
        }

        return 0;
}

static int toml_ipcp_list(toml_table_t * table,
                          enum ipcp_type type)
{
        int  i   = 0;
        int  ret = 0;

        for (i = 0; ret == 0; i++) {
                const char *       key;
                struct ipcp_info   info;
                struct ipcp_config conf;

                memset(&conf, 0, sizeof(conf));
                memset(&info, 0, sizeof(info));

                key = toml_key_in(table, i);
                if (key == NULL)
                        break;

                if (strlen(key) > IPCP_NAME_SIZE) {
                        log_err("IPCP name too long: %s,", key);
                        return -1;
                }

                info.type = type;
                strcpy(info.name, key);
                conf.type = type;

                ret = toml_ipcp(toml_table_in(table, key), &info, &conf);
        }

        return ret;
}

static int args_to_argv(const char * prog,
                        const char * args,
                        char ***     argv)
{
        char * tok;
        char * str;
        int    argc = 0;

        str = (char *) args;

        if (str != NULL) {
                tok = str;
                while (*(tok += strspn(tok, " ")) != '\0') {
                        tok  += strcspn(tok, " ");
                        argc++;
                }
        }

        *argv = malloc((argc + 2) * sizeof(**argv));
        if (*argv == NULL)
                goto fail_malloc;

        (*argv)[0] = strdup(prog);
        if ((*argv)[0] == NULL)
                goto fail_malloc2;

        argc = 1;

        if (str == NULL)
                goto finish;

        tok = str;
        while (*(tok += strspn(tok, " ")) != '\0') {
                size_t toklen = strcspn(tok, " ");
                (*argv)[argc] = malloc((toklen + 1) * sizeof(***argv));
                if ((*argv)[argc] == NULL)
                        goto fail_malloc2;

                strncpy((*argv)[argc], tok, toklen);
                (*argv)[argc++][toklen] = '\0';
                tok += toklen;
        }

 finish:
        (*argv)[argc] = NULL;

        return argc;

 fail_malloc2:
        argvfree(*argv);
 fail_malloc:
        return -1;

}

static int toml_prog(const char * prog,
                     const char * args,
                     const char * name)
{
        uint16_t flags = 0;
        int      argc;
        char **  exec;
        int      ret;

        if (args != NULL)
                flags |= BIND_AUTO;

        argc = args_to_argv(prog, args, &exec);
        if (argc < 0) {
                log_err("Failed to parse arguments: %s", args);
                return -1;
        }

        ret = bind_program(exec, name, flags);
        if (ret < 0)
                log_err("Failed to bind program %s %s for name %s.",
                        prog, args, name);

        argvfree(exec);

        return ret;
}

static int toml_prog_list(toml_array_t * progs,
                          toml_array_t * args,
                          const char *   name)
{
        int ret = 0;
        int i;

        for (i = 0; ret == 0; i++) {
                toml_datum_t prog;
                toml_datum_t arg;

                prog = toml_string_at(progs, i);
                if (!prog.ok)
                        break;

                if (args == NULL) {
                        ret = toml_prog(prog.u.s, NULL, name);
                } else {
                        arg = toml_string_at(args, i);
                        if (!arg.ok) {
                                args = NULL; /* no more arguments in list. */
                                assert(arg.u.s == NULL);
                        }

                        ret = toml_prog(prog.u.s, arg.u.s, name);

                        if (arg.ok)
                                free(arg.u.s);
                }

                free(prog.u.s);
        }

        return ret;
}

static int cp_chk_path(char * buf,
                       char * path)
{
        char * rp;

        assert(path != NULL);

        rp = realpath(path, NULL);
        if (rp == NULL) {
                log_err("Failed to check path %s: %s.", path, strerror(errno));
                goto fail_rp;
        }

        if (strlen(rp) > NAME_PATH_SIZE) {
                log_err("File path too long: %s.", rp);
                goto fail_len;
        }

        strcpy(buf, rp);
        free(rp);
        free(path);

        return 0;

 fail_len:
        free(rp);
 fail_rp:
        free(path);
        return -1;
}

static int toml_name(toml_table_t * table,
                     const char *   name)
{
        toml_array_t * progs;
        toml_array_t * args;
        toml_datum_t   lb;
        toml_datum_t   senc;
        toml_datum_t   scrt;
        toml_datum_t   skey;
        toml_datum_t   cenc;
        toml_datum_t   ccrt;
        toml_datum_t   ckey;

        struct name_info info = {
                .pol_lb = LB_SPILL
        };

        log_dbg("Found service name %s in configuration file.", name);

        if (strlen(name) > NAME_SIZE) {
                log_err("Name too long: %s", name);
                return -1;
        }

        strcpy(info.name, name);

        lb = toml_string_in(table, "lb");
        if (lb.ok) {
                if (strcmp(lb.u.s, "spill") == 0)
                        info.pol_lb = LB_SPILL;
                else if (strcmp(lb.u.s, "round-robin") == 0)
                        info.pol_lb = LB_RR;
                else
                        info.pol_lb = LB_INVALID;
                free(lb.u.s);
        }

        if (info.pol_lb == LB_INVALID) {
                log_err("Invalid load-balancing policy for %s.", name);
                return -1;
        }
        senc = toml_string_in(table, "server_enc_file");
        if (senc.ok && cp_chk_path(info.s.enc, senc.u.s) < 0)
                return -1;

        scrt = toml_string_in(table, "server_crt_file");
        if (scrt.ok && cp_chk_path(info.s.crt, scrt.u.s) < 0)
                return -1;

        skey = toml_string_in(table, "server_key_file");
        if (skey.ok && cp_chk_path(info.s.key, skey.u.s) < 0)
                return -1;

        cenc = toml_string_in(table, "client_enc_file");
        if (cenc.ok && cp_chk_path(info.c.enc, cenc.u.s) < 0)
                return -1;

        ccrt = toml_string_in(table, "client_crt_file");
        if (ccrt.ok && cp_chk_path(info.c.crt, ccrt.u.s) < 0)
                return -1;

        ckey = toml_string_in(table, "client_key_file");
        if (ckey.ok && cp_chk_path(info.c.key, ckey.u.s) < 0)
                return -1;

        if (name_create(&info) < 0) {
                log_err("Failed to create name %s.", name);
                return -1;
        }

        progs = toml_array_in(table, "prog");
        if (progs == NULL)
                return 0;

        args = toml_array_in(table, "args");
        if (toml_prog_list(progs, args, name) < 0)
                return -1;

        return 0;
}

static int toml_name_list(toml_table_t * table)
{
        int  i   = 0;
        int  ret = 0;

        for (i = 0; ret == 0; i++) {
                const char * key;

                key = toml_key_in(table, i);
                if (key == NULL)
                        break;

                ret = toml_name(toml_table_in(table, key), key);
        }

        return ret;
        return 0;
}

static int toml_toplevel(toml_table_t * table,
                         const char *   key)
{
        toml_table_t * subtable;

        subtable = toml_table_in(table, key);
        if (strcmp(key, "name") == 0)
                return toml_name_list(subtable);
        else if (strcmp(key, "local") == 0)
                return toml_ipcp_list(subtable, IPCP_LOCAL);
        else if (strcmp(key, "eth-dix") == 0)
                return toml_ipcp_list(subtable, IPCP_ETH_DIX);
        else if (strcmp(key, "eth-llc") == 0)
                return toml_ipcp_list(subtable, IPCP_ETH_LLC);
        else if (strcmp(key, "udp4") == 0)
                return toml_ipcp_list(subtable, IPCP_UDP4);
        else if (strcmp(key, "udp6") == 0)
                return toml_ipcp_list(subtable, IPCP_UDP6);
        else if (strcmp(key, "broadcast") == 0)
                return toml_ipcp_list(subtable, IPCP_BROADCAST);
        else if (strcmp(key, "unicast") == 0)
                return toml_ipcp_list(subtable, IPCP_UNICAST);
        else
                log_err("Unkown toplevel key: %s.", key);
        return -1;
}

static int toml_load(toml_table_t * table)
{
        int  i   = 0;
        int  ret = 0;

        for (i = 0; ret == 0; i++) {
                const char *   key;

                key = toml_key_in(table, i);
                if (key == NULL)
                        break;

                ret = toml_toplevel(table, key);
        }

        return ret;
}

static int toml_cfg(FILE * fp)
{
        toml_table_t * table;
        char           errbuf[ERRBUFSZ + 1];

        assert(fp != NULL);

        table = toml_parse_file(fp, errbuf, sizeof(errbuf));
        if (table == NULL) {
                log_err("Failed to parse config file: %s.", errbuf);
                goto fail_parse;
        }

        if (toml_load(table) < 0) {
                log_err("Failed to load configuration.");
                goto fail_load;
        }

        toml_free(table);

        return 0;

 fail_load:
       toml_free(table);
 fail_parse:
        return -1;
}

int irm_configure(const char * path)
{
        FILE * fp;
        char * rp;

        if (path == NULL)
                return 0;

        rp = realpath(path, NULL);
        if (rp == NULL) {
                log_err("Failed to check path for %s: %s.",
                        path, strerror(errno));
                goto fail_resolve;
        }

        log_info("Reading configuration from file %s", rp);

        fp = fopen(rp, "r");
        if (fp == NULL) {
                log_err("Failed to open config file: %s\n", strerror(errno));
                goto fail_fopen;
        }

        if (toml_cfg(fp) < 0) {
                log_err("Failed to load config file.");
                goto fail_cfg;
        }

        fclose(fp);
        free(rp);

        return 0;

 fail_cfg:
        fclose(fp);
 fail_fopen:
        free(rp);
 fail_resolve:
        return -1;
}

#endif /* HAVE_TOML */
