/* Generated by conf2struct (https://www.rutschle.net/tech/conf2struct/README)
 * on Sun Apr  6 11:44:58 2025. 

# conf2struct: generate libconf parsers that read to structs
# Copyright (C) 2018-2024  Yves Rutschle
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

*/

#define _GNU_SOURCE
#include <string.h>
#ifdef LIBCONFIG
#    include <libconfig.h>
#endif
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <stdarg.h>
#include <setjmp.h>
#include "sslh-conf.h"
#include "argtable3.h"
#ifdef LIBPCRE
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
typedef struct {
    PCRE2_SIZE rm_so;
    PCRE2_SIZE rm_eo;
} regmatch_t;
#else
#include <regex.h>
#endif


/* This gets included in the output .c file */


/* Libconfig 1.4.9 is still used by major distributions
 * (e.g. CentOS7) and had a different name for
 * config_setting_lookup */
#if LIBCONFIG_VER_MAJOR == 1
#if LIBCONFIG_VER_MINOR == 4
#if LIBCONFIG_VER_REVISION == 9
#define config_setting_lookup config_lookup_from
#endif
#endif
#endif


/* config_type, lookup_fns, type2str are related, keep them together */
typedef enum {
    CFG_BOOL,
    CFG_INT,
    CFG_INT64,
    CFG_FLOAT,
    CFG_STRING,
    CFG_GROUP,
    CFG_ARRAY,
    CFG_LIST,
} config_type;
/* /config_type */

const char* type2str[] = {
    "boolean",
    "int",
    "int64",
    "float",
    "string",
    "group",
    "array",
    "list",
};

typedef union {
    int def_bool;
    int def_int;
    long long def_int64;
    double def_float;
    char* def_string;
} any_val;

struct config_desc {
    const char* name;
    int type;
    struct config_desc * sub_group; /* Table for compound types (list and group) */
    void* arg_cl; /* command-line argument for this setting */
    void* base_addr; /* Base of the structure (filled at runtime). Probably not useable for list elements */
    size_t offset;  /* Offset of setting in the structure */
    size_t offset_len; /* Offset of *_len field, for arrays and lists */
    size_t offset_present; /* offset of *_is_present field, for optional settings */
    size_t size;   /* Size of element, or size of group for groups and lists */
    int array_type; /* type of array elements, when type == CFG_ARRAY */
    int mandatory;
    int optional;
    any_val default_val;
};

#ifndef LIBCONFIG
/* Stubs in case you don't want libconfig */

typedef void config_setting_t;
typedef int config_t;
#define CONFIG_TRUE 1
#define CONFIG_FALSE 0

#define make_config_setting_lookup(type) \
    int config_setting_lookup_##type(const config_setting_t* a, const char* b, void* c) { \
        return 0; \
    }

#define make_config_setting_get(type, ret_type) \
    ret_type config_setting_get_##type(const config_setting_t* a) { \
        return 0; \
    }

enum {
    CONFIG_TYPE_INT,
    CONFIG_TYPE_BOOL,
    CONFIG_TYPE_INT64,
    CONFIG_TYPE_FLOAT,
    CONFIG_TYPE_STRING
};

make_config_setting_lookup(bool);
make_config_setting_lookup(int);
make_config_setting_lookup(int64);
make_config_setting_lookup(float);
make_config_setting_lookup(string);

make_config_setting_get(bool, int);
make_config_setting_get(int, int);
make_config_setting_get(int64, long long int);
make_config_setting_get(float, double);
make_config_setting_get(string, char*);

config_setting_t* config_root_setting(config_t* c) {
    return NULL;
}

config_setting_t* config_lookup(config_t* c, const char* b) {
    return NULL;
}

void config_init(config_t* c) {
    return;
}

char* config_setting_name(config_setting_t* c) {
    return NULL;
}

int config_setting_is_list(config_setting_t* c) {
    return 0;
}

int config_setting_is_array(config_setting_t* c) {
    return 0;
}

int config_setting_is_scalar(config_setting_t* c) {
    return 0;
}

int config_setting_index(const config_setting_t *setting) {
    return 0;
}

config_setting_t* config_setting_lookup(config_setting_t* a, char* b) {
    return NULL;
}

int config_setting_remove(config_setting_t* cfg, char* name) {
    return 0;
}

int config_setting_type(config_setting_t* s) {
    return -1;
}

int config_setting_length(config_setting_t* a) {
    return 0;
}

config_setting_t* config_setting_get_elem(config_setting_t* a, int i) {
    return NULL;
}

int config_read_file(config_t* a, const char* b) {
    return CONFIG_TRUE;
}

int config_error_line(config_t* c) {
    return 0;
}

char* config_error_text(config_t* c) {
    return NULL;
}
#endif

static jmp_buf c2s_asprintf_fail;

static int c2s_asprintf(char **restrict strp, const char *restrict fmt, ...)
{
    va_list ap;

    va_start(ap,fmt);
    int res = vasprintf(strp, fmt, ap);
    va_end(ap);
    if (res == -1) longjmp(c2s_asprintf_fail, res);
    return res;
}

/* This is the same as config_setting_lookup_string() except
it allocates a new string which belongs to the caller */
static int myconfig_setting_lookup_stringcpy(
        const config_setting_t* setting, 
        const char* name, 
        char** value)
{
    const char* str;
    *value = NULL;
    if (config_setting_lookup_string(setting, name, &str) == CONFIG_TRUE) {
        c2s_asprintf(value, "%s", str);
        return CONFIG_TRUE;
    } else {
        return CONFIG_FALSE;
    }
}

typedef int (*lookup_fn)(const config_setting_t*, const char*, void*);
lookup_fn lookup_fns[] = {
    (lookup_fn)config_setting_lookup_bool,
    (lookup_fn)config_setting_lookup_int,
    (lookup_fn)config_setting_lookup_int64,
    (lookup_fn)config_setting_lookup_float,
    (lookup_fn)myconfig_setting_lookup_stringcpy,
    NULL,  /* CFG_GROUP */
    NULL,  /* CFG_ARRAY */
    NULL,  /* CFG_LIST */
};

/* Copy an any_val to arbitrary memory location */
/* 0: success
 * <0: error */
static int any_valcpy(config_type type, void* target, any_val val)
{
    switch(type) {
    case CFG_BOOL:
        *(int*)target = val.def_bool;
        break;

    case CFG_INT:
        *(int*)target = val.def_int;
        break;

    case CFG_INT64:
        *(long long*)target = val.def_int64;
        break;

    case CFG_FLOAT:
        *(double*)target = val.def_float;
        break;

    case CFG_STRING:
        *(char**)target = val.def_string;
        break;

    default:
        fprintf(stderr, "Unknown type specification %d\n", type);
        return -1;
    }
    return 1;
}


/* Copy the value of a setting to an arbitrary memory that
* must be large enough */
/* 0: success
 * <0: error */
static int settingcpy(config_type type, void* target, const config_setting_t* setting)
{
    any_val val;
    char* str;

    switch(type) {
    case CFG_BOOL:
        val.def_bool = config_setting_get_bool(setting);
        *(int*)target = val.def_bool;
        break;

    case CFG_INT:
        val.def_int = config_setting_get_int(setting);
        *(int*)target = val.def_int;
        break;

    case CFG_INT64:
        val.def_int64 = config_setting_get_int64(setting);
        *(long long*)target = val.def_int64;
        break;

    case CFG_FLOAT:
        val.def_float = config_setting_get_float(setting);
        *(double*)target = val.def_int64;
        break;

    case CFG_STRING:
        c2s_asprintf(&str, "%s", config_setting_get_string(setting));
        val.def_string = str;
        *(char**)target = val.def_string;
        break;

    default:
        fprintf(stderr, "Unknown type specification %d\n", type);
        return -1;
    }
    return 0;
}

/* Copy the value of a command line arg to arbitrary memory
* that must be large enough for the type */
/* 0: success
 * <0: error */
static int clcpy(config_type type, void* target, const void* cl_arg)
{
    any_val val;
    char* str;

    switch(type) {
    case CFG_BOOL:
        val.def_bool = (*(struct arg_lit**)cl_arg)->count;
        *(int*)target = val.def_bool;
        break;

    case CFG_INT:
        val.def_int = (*(struct arg_int**)cl_arg)->ival[0];
        *(int*)target = val.def_int;
        break;

    case CFG_INT64:
        val.def_int64 = (*(struct arg_int**)cl_arg)->ival[0];
        *(long long*)target = val.def_int64;
        break;

    case CFG_FLOAT:
        val.def_float = (*(struct arg_dbl**)cl_arg)->dval[0];
        *(double*)target = val.def_float;
        break;

    case CFG_STRING:
        c2s_asprintf(&str, "%s", (*(struct arg_str**)cl_arg)->sval[0]);
        val.def_string = str;
        *(char**)target = val.def_string;
        break;

    default:
        fprintf(stderr, "Unknown type specification %d\n", type);
        return -1;
    }
    return 0;
}

/* Copy the value of a string argument to arbitary memory
* location that must be large enough, converting on the way
* (i.e. CFG_INT gets atoi() and so on) */
/* 0: success
 * <0: error */
static int stringcpy(config_type type, void* target, char* from)
{
    any_val val;
    
    switch(type) {
    case CFG_BOOL:
        val.def_bool = (*from != '0');
        *(int*)target = val.def_bool;
        break;

    case CFG_INT:
        val.def_int = strtol(from, NULL, 10);
        *(int*)target = val.def_int;
        break;

    case CFG_INT64:
        val.def_int64 = strtoll(from, NULL, 10);
        *(long long*)target = val.def_int64;
        break;

    case CFG_FLOAT:
        val.def_float = strtod(from, NULL);
        *(double*)target = val.def_float;
        break;

    case CFG_STRING:
        val.def_string = from;
        *(char**)target = val.def_string;
        break;

    default:
        fprintf(stderr, "Unknown type specification %d\n", type);
        return -1;
    }
    return 0;
}


/* Element to describe the target of a compound element
* element: which config entry is being changed
* match: if >0, index in pmatch to set
*        if 0, don't match but init with value
* value: constant if not matching */
struct compound_cl_target {
    struct config_desc * element;
    int match;
    any_val value;
};

/* Element to describe one compound command line argument
 * An argument is string that gets matched against a regex,
 * then match-groups get evaluated to each targets[].
 * For lists, base_entry points to the config_setting so we
 * can append to it */
struct compound_cl_arg {
    const char* regex;
    struct arg_str** arg_cl; /* arg_str entry for this compound option */
    struct config_desc * base_entry;
    struct compound_cl_target* targets;

    /* If override_desc is set, it points to the descriptor of the element in
    the group which will be checked for override. Then, override_matchindex
    indicates the command-line parameter match used to compare against
    override_desc to know if this group is overridden. If override_matchindex
    is 0, we don't match from the command-line but from a constant stored in
    override_const instead */
    struct config_desc * override_desc;
    int override_matchindex;
    char* override_const;
};


struct arg_file* sslhcfg_conffile;
 struct arg_int* sslhcfg_verbose;
 struct arg_int* sslhcfg_verbose_config;
 struct arg_int* sslhcfg_verbose_config_error;
 struct arg_int* sslhcfg_verbose_connections;
 struct arg_int* sslhcfg_verbose_connections_try;
 struct arg_int* sslhcfg_verbose_connections_error;
 struct arg_int* sslhcfg_verbose_fd;
 struct arg_int* sslhcfg_verbose_packets;
 struct arg_int* sslhcfg_verbose_probe_info;
 struct arg_int* sslhcfg_verbose_probe_error;
 struct arg_int* sslhcfg_verbose_system_error;
 struct arg_int* sslhcfg_verbose_int_error;
 struct arg_lit* sslhcfg_version;
 struct arg_lit* sslhcfg_foreground;
 struct arg_lit* sslhcfg_inetd;
 struct arg_lit* sslhcfg_numeric;
 struct arg_lit* sslhcfg_transparent;
 struct arg_int* sslhcfg_timeout;
 struct arg_int* sslhcfg_udp_max_connections;
 struct arg_str* sslhcfg_user;
 struct arg_str* sslhcfg_pidfile;
 struct arg_str* sslhcfg_chroot;
 struct arg_str* sslhcfg_syslog_facility;
 struct arg_str* sslhcfg_logfile;
 struct arg_str* sslhcfg_on_timeout;
 struct arg_str* sslhcfg_prefix;
 	struct arg_str* sslhcfg_listen;
 	struct arg_str* sslhcfg_ssh;
 	struct arg_str* sslhcfg_tls;
 	struct arg_str* sslhcfg_ssl;
 	struct arg_str* sslhcfg_openvpn;
 	struct arg_str* sslhcfg_tinc;
 	struct arg_str* sslhcfg_wireguard;
 	struct arg_str* sslhcfg_xmpp;
 	struct arg_str* sslhcfg_http;
 	struct arg_str* sslhcfg_adb;
 	struct arg_str* sslhcfg_socks5;
 	struct arg_str* sslhcfg_syslog;
 	struct arg_str* sslhcfg_msrdp;
 	struct arg_str* sslhcfg_anyprot;
 struct arg_end* sslhcfg_end;

                                                                                                                                                                          
static struct config_desc table_sslhcfg_protocols[] = {


        { 
            /* name */          "name", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, name),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     1, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_string = NULL 
        },

        { 
            /* name */          "host", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, host),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     1, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_string = NULL 
        },

        { 
            /* name */          "port", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, port),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     1, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_string = NULL 
        },

        { 
            /* name */          "service", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, service),
            /* offset_len */    0,
            /* offset_present */ offsetof(struct sslhcfg_protocols_item, service_is_present),
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      1, 
            /* default_val*/    .default_val.def_string = NULL 
        },

        { 
            /* name */          "is_unix", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, is_unix),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "is_udp", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, is_udp),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "udp_timeout", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, udp_timeout),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 60 
        },

        { 
            /* name */          "fork", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, fork),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "tfo_ok", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, tfo_ok),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "transparent", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, transparent),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "resolve_on_forward", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, resolve_on_forward),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "log_level", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, log_level),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 1 
        },

        { 
            /* name */          "keepalive", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, keepalive),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "sni_hostnames", 
            /* type */          CFG_ARRAY, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, sni_hostnames),
            /* offset_len */    offsetof(struct sslhcfg_protocols_item, sni_hostnames_len),
            /* offset_present */ 0,
            /* size */          sizeof(char*), 
            /* array_type */    CFG_STRING,
            /* mandatory */     1, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 0 
        },

        { 
            /* name */          "alpn_protocols", 
            /* type */          CFG_ARRAY, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, alpn_protocols),
            /* offset_len */    offsetof(struct sslhcfg_protocols_item, alpn_protocols_len),
            /* offset_present */ 0,
            /* size */          sizeof(char*), 
            /* array_type */    CFG_STRING,
            /* mandatory */     1, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 0 
        },

        { 
            /* name */          "regex_patterns", 
            /* type */          CFG_ARRAY, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, regex_patterns),
            /* offset_len */    offsetof(struct sslhcfg_protocols_item, regex_patterns_len),
            /* offset_present */ 0,
            /* size */          sizeof(char*), 
            /* array_type */    CFG_STRING,
            /* mandatory */     1, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 0 
        },

        { 
            /* name */          "minlength", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, minlength),
            /* offset_len */    0,
            /* offset_present */ offsetof(struct sslhcfg_protocols_item, minlength_is_present),
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      1, 
            /* default_val*/    .default_val.def_int = 0 
        },

        { 
            /* name */          "proxyprotocol", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_protocols_item, proxyprotocol),
            /* offset_len */    0,
            /* offset_present */ offsetof(struct sslhcfg_protocols_item, proxyprotocol_is_present),
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      1, 
            /* default_val*/    .default_val.def_int = 0 
        },
	{ 0 }
};
                                                      
static struct config_desc table_sslhcfg_listen[] = {


        { 
            /* name */          "host", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_listen_item, host),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     1, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_string = NULL 
        },

        { 
            /* name */          "port", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_listen_item, port),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     1, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_string = NULL 
        },

        { 
            /* name */          "is_udp", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_listen_item, is_udp),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "is_unix", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_listen_item, is_unix),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "keepalive", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_listen_item, keepalive),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },
	{ 0 }
};
                                                                                                                                                                                                                                           
static struct config_desc table_sslhcfg[] = {


        { 
            /* name */          "verbose", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_verbose,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, verbose),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 0 
        },

        { 
            /* name */          "verbose_config", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_verbose_config,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, verbose_config),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 0 
        },

        { 
            /* name */          "verbose_config_error", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_verbose_config_error,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, verbose_config_error),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 3 
        },

        { 
            /* name */          "verbose_connections", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_verbose_connections,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, verbose_connections),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 3 
        },

        { 
            /* name */          "verbose_connections_try", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_verbose_connections_try,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, verbose_connections_try),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 0 
        },

        { 
            /* name */          "verbose_connections_error", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_verbose_connections_error,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, verbose_connections_error),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 3 
        },

        { 
            /* name */          "verbose_fd", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_verbose_fd,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, verbose_fd),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 0 
        },

        { 
            /* name */          "verbose_packets", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_verbose_packets,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, verbose_packets),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 0 
        },

        { 
            /* name */          "verbose_probe_info", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_verbose_probe_info,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, verbose_probe_info),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 0 
        },

        { 
            /* name */          "verbose_probe_error", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_verbose_probe_error,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, verbose_probe_error),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 3 
        },

        { 
            /* name */          "verbose_system_error", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_verbose_system_error,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, verbose_system_error),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 3 
        },

        { 
            /* name */          "verbose_int_error", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_verbose_int_error,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, verbose_int_error),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 3 
        },

        { 
            /* name */          "version", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_version,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, version),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "foreground", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_foreground,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, foreground),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "inetd", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_inetd,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, inetd),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "numeric", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_numeric,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, numeric),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "transparent", 
            /* type */          CFG_BOOL, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_transparent,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, transparent),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_bool = 0 
        },

        { 
            /* name */          "timeout", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_timeout,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, timeout),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 5 
        },

        { 
            /* name */          "udp_max_connections", 
            /* type */          CFG_INT, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_udp_max_connections,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, udp_max_connections),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(int), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 1024 
        },

        { 
            /* name */          "user", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_user,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, user),
            /* offset_len */    0,
            /* offset_present */ offsetof(struct sslhcfg_item, user_is_present),
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      1, 
            /* default_val*/    .default_val.def_string = NULL 
        },

        { 
            /* name */          "pidfile", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_pidfile,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, pidfile),
            /* offset_len */    0,
            /* offset_present */ offsetof(struct sslhcfg_item, pidfile_is_present),
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      1, 
            /* default_val*/    .default_val.def_string = NULL 
        },

        { 
            /* name */          "chroot", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_chroot,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, chroot),
            /* offset_len */    0,
            /* offset_present */ offsetof(struct sslhcfg_item, chroot_is_present),
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      1, 
            /* default_val*/    .default_val.def_string = NULL 
        },

        { 
            /* name */          "syslog_facility", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_syslog_facility,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, syslog_facility),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_string = "auth" 
        },

        { 
            /* name */          "logfile", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_logfile,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, logfile),
            /* offset_len */    0,
            /* offset_present */ offsetof(struct sslhcfg_item, logfile_is_present),
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      1, 
            /* default_val*/    .default_val.def_string = NULL 
        },

        { 
            /* name */          "on_timeout", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_on_timeout,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, on_timeout),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_string = "ssh" 
        },

        { 
            /* name */          "prefix", 
            /* type */          CFG_STRING, 
            /* sub_group*/      NULL,
            /* arg_cl */        & sslhcfg_prefix,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, prefix),
            /* offset_len */    0,
            /* offset_present */ 0,
            /* size */          sizeof(char*), 
            /* array_type */    -1,
            /* mandatory */     0, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_string = "" 
        },

        { 
            /* name */          "listen", 
            /* type */          CFG_LIST, 
            /* sub_group*/      table_sslhcfg_listen,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, listen),
            /* offset_len */    offsetof(struct sslhcfg_item, listen_len),
            /* offset_present */ 0,
            /* size */          sizeof(struct sslhcfg_listen_item), 
            /* array_type */    -1,
            /* mandatory */     1, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 0 
        },

        { 
            /* name */          "protocols", 
            /* type */          CFG_LIST, 
            /* sub_group*/      table_sslhcfg_protocols,
            /* arg_cl */        NULL,
            /* base_addr */     NULL,
            /* offset */        offsetof(struct sslhcfg_item, protocols),
            /* offset_len */    offsetof(struct sslhcfg_item, protocols_len),
            /* offset_present */ 0,
            /* size */          sizeof(struct sslhcfg_protocols_item), 
            /* array_type */    -1,
            /* mandatory */     1, 
            /* optional */      0, 
            /* default_val*/    .default_val.def_int = 0 
        },
	{ 0 }
};
static struct compound_cl_target sslhcfg_anyprot_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "anyprot" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_msrdp_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "msrdp" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_syslog_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "syslog" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_socks5_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "socks5" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_adb_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "adb" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_http_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "http" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_xmpp_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "xmpp" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_wireguard_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "wireguard" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_tinc_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tinc" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_openvpn_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "openvpn" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_ssl_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_tls_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "tls" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_ssh_targets [] = {
	{ & table_sslhcfg_protocols[0], 0, .value.def_string = "ssh" },
	{ & table_sslhcfg_protocols[1], 1, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[2], 2, .value.def_string = "0" },
	{ & table_sslhcfg_protocols[7], 0, .value.def_bool = 1 },
	{ & table_sslhcfg_protocols[11], 0, .value.def_int = 1 },
	{ & table_sslhcfg_protocols[8], 0, .value.def_bool = 1 },
	{ 0 }
};

static struct compound_cl_target sslhcfg_listen_targets [] = {
	{ & table_sslhcfg_listen[0], 1, .value.def_string = "0" },
	{ & table_sslhcfg_listen[1], 2, .value.def_string = "0" },
	{ 0 }
};

static struct compound_cl_arg compound_cl_args[] = {
        {   /* arg: listen */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_listen,
            .base_entry =      & table_sslhcfg [26],
            .targets =         sslhcfg_listen_targets,


            .override_desc =   NULL,
            .override_matchindex = 0,
            .override_const = NULL,
        },

        {   /* arg: ssh */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_ssh,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_ssh_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "ssh",
        },

        {   /* arg: tls */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_tls,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_tls_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "tls",
        },

        {   /* arg: ssl */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_ssl,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_ssl_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "tls",
        },

        {   /* arg: openvpn */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_openvpn,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_openvpn_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "openvpn",
        },

        {   /* arg: tinc */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_tinc,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_tinc_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "tinc",
        },

        {   /* arg: wireguard */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_wireguard,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_wireguard_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "wireguard",
        },

        {   /* arg: xmpp */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_xmpp,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_xmpp_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "xmpp",
        },

        {   /* arg: http */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_http,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_http_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "http",
        },

        {   /* arg: adb */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_adb,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_adb_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "adb",
        },

        {   /* arg: socks5 */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_socks5,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_socks5_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "socks5",
        },

        {   /* arg: syslog */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_syslog,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_syslog_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "syslog",
        },

        {   /* arg: msrdp */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_msrdp,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_msrdp_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "msrdp",
        },

        {   /* arg: anyprot */
            .regex =           "(.+):(\\w+)",
            .arg_cl =          & sslhcfg_anyprot,
            .base_entry =      & table_sslhcfg [27],
            .targets =         sslhcfg_anyprot_targets,


            .override_desc =   & table_sslhcfg_protocols [0],
            .override_matchindex = 0,
            .override_const = "anyprot",
        },

	{ 0 }
};


/* Enable debug to follow the parsing of tables */
#if 0
#define TRACE_READ(x) printf x
#define TRACE_READ_PRINT_SETTING 1
#else
#define TRACE_READ(x)
#define TRACE_READ_PRINT_SETTING 0
#endif

/* Enable debug to follow the parsing of compound options */
#if 0
#define TRACE_CMPD(x) printf x
#define TRACE_CMPD_PRINT_SETTING 1
#else
#define TRACE_CMPD(x)
#define TRACE_CMPD_PRINT_SETTING 0
#endif

static void print_setting(config_type type, void* val)
{
    if (TRACE_READ_PRINT_SETTING || TRACE_CMPD_PRINT_SETTING) {
        switch(type) {
        case CFG_BOOL:
        case CFG_INT:
            printf("%d", *(int*)val);
            break;
        case CFG_INT64:
            printf("%lld", *(long long*)val);
            break;
        case CFG_FLOAT:
            printf("%f", *(double*)val);
            break;
        case CFG_STRING:
            printf("`%s'", *(char**)val);
            break;
        case CFG_GROUP:
        case CFG_LIST:
        case CFG_ARRAY:
            break;
        }
    }
}

/* Changes all dashes to underscores in a string of
* vice-versa */
static void strswap_ud(const char target, char* str)
{
    char* c;
    for (c = str; *c; c++)
        if (*c == (target == '_' ? '-' : '_'))
             *c = (target == '_' ? '_' : '-');
}

/* Same as config_setting_lookup() but looks up with dash or
* underscore so `my_setting` and `my-setting` match the same */
static config_setting_t* config_setting_lookup_ud(config_setting_t* cfg, struct config_desc* desc)
{
    config_setting_t* setting;
    char name[strlen(desc->name)+1];;
    strcpy(name, desc->name);

    strswap_ud('_', name);
    setting = config_setting_lookup(cfg, name);
    if (setting)
        return setting;

    strswap_ud('-', name);
    setting = config_setting_lookup(cfg, name);
    return setting;
}

static int lookup_typed_ud(config_setting_t* cfg, void* target, struct config_desc *desc)
{
    lookup_fn lookup_fn = lookup_fns[desc->type];
    char name[strlen(desc->name)+1];;
    strcpy(name, desc->name);

    strswap_ud('_', name);
    if (lookup_fn(cfg, name, ((char*)target) + desc->offset) == CONFIG_TRUE)
        return CONFIG_TRUE;

    strswap_ud('-', name);
    return lookup_fn(cfg, name, ((char*)target) + desc->offset);
}

/* Removes a setting, trying both underscores and dashes as
* name (so deleting 'my-setting' deletes both 'my_setting'
* and 'my-setting') */
static int setting_delete_ud(config_setting_t* cfg, struct config_desc *desc)
{
    char name[strlen(desc->name)+1];;
    strcpy(name, desc->name);

    strswap_ud('_', name);
    if (config_setting_remove(cfg, name) == CONFIG_TRUE)
        return CONFIG_TRUE;

    strswap_ud('-', name);
    return config_setting_remove(cfg, name);
}

/* When traversing configuration, allocate memory for plural
* types, init for scalars */
static void read_block_init(void* target, config_setting_t* cfg, struct config_desc* desc)
{
    size_t len = 0;
    void* block;
    config_setting_t* setting;

    switch (desc->type) {
    case CFG_LIST:
    case CFG_ARRAY:
        if (cfg) {
            setting = config_setting_lookup_ud(cfg, desc);
            if (setting)
                len = config_setting_length(setting);
        } 
        block = calloc(len, desc->size);

        *(size_t*)(((char*)target) + desc->offset_len) = len;
        *(void**)(((char*)target) + desc->offset) = block;
        TRACE_READ((" sizing for %zu elems ", len));
        break;

    case CFG_GROUP:
        block = calloc(1, desc->size);
        *(void**)(((char*)target) + desc->offset) = block;
        TRACE_READ((" sizing for %zu elems ", len));
        break;

    default: 
        /* scalar types: copy default */
        memcpy(((char*)target) + desc->offset, &desc->default_val, desc->size);
        TRACE_READ(("setting %s to default ", desc->name));
        print_setting(desc->type,(char*)target + desc->offset);
        break;
    }
}

static int read_block(config_setting_t* cfg, 
                      void* target, 
                      struct config_desc* desc, 
                      char** errmsg);

/* When traversing configuration, set value from config
* file, or command line 
* return: 0 if not set, 1 if set somehow */
static int read_block_setval(void* target, 
                             config_setting_t* cfg, 
                             struct config_desc* desc, 
                             char** errmsg)
{
    int i;
    size_t len = 0;
    void* block;
    int in_cfg = 0, in_cl = 0; /* Present in config file?  present on command line? */
    config_setting_t* setting = NULL;

    switch (desc->type) {
    case CFG_LIST:
        if (cfg) {
            setting = config_setting_lookup_ud(cfg, desc);
            if (setting) 
                len = config_setting_length(setting);
            block = *(void**)(((char*)target) + desc->offset);
            for (i = 0; i < len; i++) {
                config_setting_t* elem = config_setting_get_elem(setting, i);
                if (!read_block(elem, (char*)block + desc->size * i, desc->sub_group, errmsg))
                    return 0;
            }
        }
        break;

    case CFG_ARRAY:
        if (cfg) {
            setting = config_setting_lookup_ud(cfg, desc);
            if (setting)
                len = config_setting_length(setting);
            block = *(void**)(((char*)target) + desc->offset);
            for (i = 0; i < len; i++) {
                config_setting_t* elem = config_setting_get_elem(setting, i);
                settingcpy(desc->array_type, (char*)block + desc->size * i, elem);
                TRACE_READ(("[%d] = ", i));
                print_setting(desc->array_type, (char*)block + desc->size *i); TRACE_READ(("\n"));
            }
            setting_delete_ud(cfg, desc);
        }
        break;

    case CFG_GROUP:
        if (cfg) setting = config_setting_lookup_ud(cfg, desc);
        block = *(void**)(((char*)target) + desc->offset);
        if (!read_block(setting, block, desc->sub_group, errmsg)) return 0;
        break;

    default: /* scalar types */
        TRACE_READ((" `%s'", desc->name));
        if (cfg && config_setting_lookup_ud(cfg, desc)) {
            TRACE_READ((" in config file: "));
            /* setting is present in cfg, look it up */
            if (lookup_typed_ud(cfg, target, desc) != CONFIG_TRUE) {
                TRACE_READ((" but wrong type (expected %s) ", type2str[desc->type]));
                c2s_asprintf(errmsg, "Option \"%s\" wrong type, expected %s\n", 
                    desc->name, type2str[desc->type]);
                return 0;
            }
            print_setting(desc->type, (((char*)target) + desc->offset));
            setting_delete_ud(cfg, desc);
            in_cfg = 1;
        } else {
            TRACE_READ((" not in config file"));
        }
        if (desc->arg_cl && (*(struct arg_int**)desc->arg_cl)->count) {
            clcpy(desc->type, ((char*)target) + desc->offset, desc->arg_cl);
            TRACE_READ((", command line sets to "));
            print_setting(desc->type, (((char*)target) + desc->offset));
            in_cl = 1;
        } else {
            TRACE_READ((", not in command line"));
        }
        if (!(in_cfg || in_cl)) {
            TRACE_READ(("\n"));
            return 0;
        }
        TRACE_READ(("\n"));
        break;
    }
    return 1;
}

/* Set *_is_present flag for target */
static void target_is_present(void* target, struct config_desc* desc, int val)
{
    if (desc->optional) {  /* _is_present only exists in target for optional settings */
        TRACE_READ((" mark as set"));
        *(int*)((char*)target + desc->offset_present) = val;
    }
}

/* traverses the configuration; allocates memory if needed,
* set to default if exists,
* fill from configuration file if present, overrides or set from
* command line if present, verifies mandatory options have
* been set
* target: base address of the group being processed
*/
static int read_block(config_setting_t* cfg, void* target, struct config_desc* desc, char** errmsg)
{
    int set;

    for (; desc->name; desc++) {
        TRACE_READ(("reading %s%s%s: ", desc->optional ? "optional " : "", desc->mandatory ? "mandatory " : "",  desc->name));
        desc->base_addr = target;


        read_block_init(target, cfg, desc);
        set = read_block_setval(target, cfg, desc, errmsg);

        if (!set && desc->mandatory) {
            c2s_asprintf(errmsg, "Mandatory option \"%s\" not found", desc->name);
            return 0;
        }

        if (desc->optional) target_is_present(target, desc, set && desc->optional);
    }
    return 1;
}

/* Copy regex match into newly allocated string
 * out: newly allocated string (caller has to free it)
 * in: string into which the match was made
 * pmatch: the match to extract */
static void pmatchcpy(char** out, const char* in, regmatch_t* pmatch)
{
    int len = pmatch->rm_eo - pmatch->rm_so;
    *out = calloc(len+1, 1);
    memcpy(*out, in + pmatch->rm_so, len);
}

/* Processes a list of targets within one element, setting
* the values in the target setting 
* target: where to put the data
* arg: CL arg containing the target fields
* clval: command line parameter
* pmatch: regex match array into clval
*/
static int set_target_fields(void* target_addr, struct compound_cl_arg* arg, const char* clval, regmatch_t* pmatch)
{
    int pmatch_cnt = 1;
    struct compound_cl_target* target;

    for (target = arg->targets; target->element; target++) {
        struct config_desc * element = target->element;
        if (target->match) {
            TRACE_CMPD(("    match %d rm_so %d rm_eo %d type %d\n", 
                        pmatch_cnt, pmatch[pmatch_cnt].rm_so, pmatch[pmatch_cnt].rm_eo, element->type ));
            if (pmatch[pmatch_cnt].rm_so == -1) {
                /* This should not happen as regexec() did
                * match before, unless there is a
                * discrepency between the regex and the
                * number of backreferences */
                return 0;
            }
            char* str;
            pmatchcpy(&str, clval, &pmatch[pmatch_cnt]);

            stringcpy(element->type, (char*)target_addr + element->offset, str);
            TRACE_CMPD(("setting %p+%zu to : ", target_addr , element->offset));
            print_setting(element->type , (char*)target_addr + element->offset);
            TRACE_CMPD(("\n"));

            /* str is temporary buffer for type conversion, except for strings which we
            * need to keep around so don't free them */
            if (element->type != CFG_STRING)
                free(str);
            pmatch_cnt++;
        } else { /* don't use matching, set constant */
            any_valcpy(element->type, (char*)target_addr + element->offset,
                        target->value);
        }
    }

    return 1;
}

/* Goes over a list, finds if a group matches the specified string and overwrite
* it if it does. */
static int override_on_str(struct compound_cl_arg* arg, const char* str, regmatch_t* pmatch)
{
    struct config_desc * desc = arg->base_entry;
    void* list_base = *(void**)(desc->base_addr + desc->offset);
    size_t list_len = *(size_t*)(desc->base_addr + desc->offset_len);
    size_t elem_size = desc->size;
    int i;

    for (i = 0; i < list_len; i++) {
        char* group_base = ((char*)list_base + i * elem_size);

        char* cfg_member = *(char**)(group_base + arg->override_desc->offset);
        if (!strcmp(str, cfg_member)) {
            memset(group_base, 0, elem_size);
            struct arg_str* arg_cl = *arg->arg_cl;
            if (!set_target_fields(group_base, arg, arg_cl->sval[0], pmatch))
                return 0;
            return 1;
        }
    }
    return 0;
}

/* Goes over a list and override group if needed */
static int override_elem(struct compound_cl_arg* arg, int arg_index, regmatch_t* pmatch) 
{
    char* str;
    int allocated = 0;
    int res;

    if (arg->override_matchindex) {
        struct arg_str* arg_cl = *arg->arg_cl;
        pmatchcpy(&str, arg_cl->sval[arg_index], &pmatch[arg->override_matchindex]);
        allocated = 1;
    } else {
        str = arg->override_const;
    }

    res = override_on_str(arg, str, pmatch);

    if (allocated) free(str);

    return res;
}

/* Add an argument to a list, overriding if required or
* appending otherwise */
static int add_arg_to_list(struct compound_cl_arg* arg, int arg_index, regmatch_t* pmatch)
{
    struct config_desc * desc = arg->base_entry;
    void* list_base = *(void**)(desc->base_addr + desc->offset);
    size_t list_len = *(size_t*)(desc->base_addr + desc->offset_len);
    size_t elem_size = desc->size;

    /* are we overriding an existing group? */
    if (arg->override_desc)
        if (override_elem(arg, arg_index, pmatch))
            return 1;

    /* override not found or no override, append element and * zero it out */
    list_len++;
    list_base = realloc(list_base, list_len * elem_size);
    *(size_t*)(desc->base_addr + desc->offset_len) = list_len;
    *(void**)(desc->base_addr + desc->offset) = list_base;
    memset(list_base + (list_len - 1) * elem_size, 0, elem_size);

    struct arg_str* arg_cl = *arg->arg_cl;
    if (!set_target_fields((char*)list_base + (list_len - 1) * elem_size, arg, arg_cl->sval[arg_index], pmatch)) {
        return 0;
    }
    return 1;
}

/* TODO: pass pmatch size as parameter or something */
#define MAX_MATCH 10

#ifndef LIBPCRE
static int regcompmatch_posix( regmatch_t* pmatch, 
                        struct compound_cl_arg* arg, 
                        int arg_index, 
                        char** errmsg)
{
    char* regerr;
    struct arg_str* arg_cl = *arg->arg_cl;
    regex_t preg;
    int res = regcomp(&preg, arg->regex, REG_EXTENDED);
    if (res) {
        int errlen = regerror(res, &preg, NULL, 0);
        regerr = malloc(errlen);
        regerror(res, &preg, regerr, errlen);
        c2s_asprintf(errmsg, "compiling pattern /%s/:%s", arg->regex, regerr);
        free(regerr);
        return 0;
    }
    res = regexec(&preg, arg_cl->sval[arg_index], MAX_MATCH, &pmatch[0], 0);
    if (res) {
        c2s_asprintf(errmsg, "--%s %s: Illegal argument", 
        arg_cl->hdr.longopts, 
        arg->regex); 
        return 0;
    }
    return 1;
}
#endif


#ifdef LIBPCRE
static int regcompmatch_pcre2( regmatch_t* pmatch,
                        struct compound_cl_arg* arg,
                        int arg_index,
                        char** errmsg)
{
    int i, error;
    pcre2_code* pcre;
    PCRE2_UCHAR8 err_str[120];  /* ample, according to pcre2api(3) */
    pcre2_match_data* matches;
    PCRE2_SIZE error_offset;
    struct arg_str* arg_cl = *arg->arg_cl;

    pcre = pcre2_compile((PCRE2_SPTR8)arg->regex, PCRE2_ZERO_TERMINATED, 0, &error, &error_offset, NULL);
    if (!pcre) {
        pcre2_get_error_message(error, err_str, sizeof(err_str));
        c2s_asprintf(errmsg, "compiling pattern /%s/:%d: %s at offset %ld\n",
                arg->regex, error, err_str, error_offset);
        return 0;
    }

    matches = pcre2_match_data_create(MAX_MATCH, NULL);
    int res = pcre2_match(pcre, (PCRE2_SPTR8)arg_cl->sval[arg_index], PCRE2_ZERO_TERMINATED,
                          0, 0, matches, NULL);
    if (res < 0) {
        pcre2_get_error_message(res, err_str, sizeof(err_str));
        c2s_asprintf(errmsg, "matching %s =~ /%s/:%d: %s\n", 
                          arg_cl->sval[arg_index], arg->regex, res, err_str);
        return 0;
    }

    PCRE2_SIZE *ovec = pcre2_get_ovector_pointer(matches);
    if (res > MAX_MATCH) res = MAX_MATCH;
    /* From pcre2posix.c */
    for (i = 0; i < res; i++) {
        pmatch[i].rm_so = (ovec[i*2] == PCRE2_UNSET) ? -1 : ovec[i*2];
        pmatch[i].rm_eo = (ovec[i*2+1] == PCRE2_UNSET) ? -1 : ovec[i*2+1];
    }
    for (; i < MAX_MATCH; i++) pmatch[i].rm_so = pmatch[i].rm_eo = -1;

    pcre2_match_data_free(matches);
    return 1;
}
#endif


/* Regex fiddling: uses info in arg to fill pmatch
* arg: description of the command line argument
* arg_index: occurence of this argument on the command line
*/
static int regcompmatch(regmatch_t* pmatch,
                        struct compound_cl_arg* arg,
                        int arg_index,
                        char** errmsg)
{
#if LIBPCRE
    return regcompmatch_pcre2(pmatch, arg, arg_index, errmsg);
#else
    return regcompmatch_posix(pmatch, arg, arg_index, errmsg);
#endif
}

/* Read compound options described in `arg`, from `cfg`, to `setting` */
static int read_compounds(config_setting_t* cfg, 
                          void* setting, 
                          struct compound_cl_arg* arg, 
                          char** errmsg)
{
    int arg_i;
    struct arg_str* arg_cl;
    regmatch_t pmatch[MAX_MATCH];

    for (; arg->regex; arg++) {
        arg_cl = *arg->arg_cl;
        TRACE_CMPD(("Compound %s occurs %d : ", arg_cl->hdr.longopts, arg_cl->count));
        for (arg_i = 0; arg_i < arg_cl->count; arg_i++) {
            if (!regcompmatch(&pmatch[0], arg, arg_i, errmsg))
                return 0;
            TRACE_CMPD(("`%s' matched\n", arg_cl->sval[arg_i]));

            switch (arg->base_entry->type) {
            case CFG_LIST:
                /* In a list, find the end or the element to override */
                if (!add_arg_to_list(arg, arg_i, pmatch)) {
                    return 0;
                }
                break;

            /* Semantics for CFG_ARRAY TBD */

            case CFG_GROUP:
                if (!set_target_fields(
                            /* base_addr is the same for all elements in the group */
                            arg->targets[0].element->base_addr, 
                            arg, 
                            arg_cl->sval[arg_i], 
                            pmatch))
                    return 0;

            default:
                TRACE_CMPD(("error, compound on type %d\n", arg->base_entry->type));
                break;
            }
        }
        TRACE_CMPD(("done %s\n", arg_cl->hdr.longopts));
    }
    return 1;
}

/* read config file `filename` into `c` */
static int c2s_parse_file(const char* filename, config_t* c, char**errmsg)
{
    /* Read config file */
    if (config_read_file(c, filename) == CONFIG_FALSE) {
        if (config_error_line(c) != 0) {
           c2s_asprintf(errmsg, "%s:%d:%s", 
                    filename,
                    config_error_line(c),
                    config_error_text(c));
           return 0;
        }
        c2s_asprintf(errmsg, "%s:%s", filename, config_error_text(c));
        return 0;
    }
    return 1;
}

/* Allocates a new string that represents the setting value, which must be a scalar */
static void scalar_to_string(char** strp, config_setting_t* s)
{
    switch(config_setting_type(s)) {
    case CONFIG_TYPE_INT:
        c2s_asprintf(strp, "%d\n", config_setting_get_int(s));
        break;

    case CONFIG_TYPE_BOOL:
        c2s_asprintf(strp, "%s\n", config_setting_get_bool(s) ?  "[true]" : "[false]" );
        break;

    case CONFIG_TYPE_INT64:
        c2s_asprintf(strp, "%lld\n", config_setting_get_int64(s));
        break;

    case CONFIG_TYPE_FLOAT:
        c2s_asprintf(strp, "%lf\n", config_setting_get_float(s));
        break;

    case CONFIG_TYPE_STRING:
        c2s_asprintf(strp, "%s\n", config_setting_get_string(s));
        break;

    default: /* This means a bug */
        fprintf(stderr, "Unexpected type %d\n", config_setting_type(s));
        exit(1);
    }
}

/* Typesets all the settings in a configuration as a
* newly-allocated string. The string management is caller's
* responsability. 
* Returns the number of scalars in the configuration */
static int cfg_as_string(config_setting_t* parent, const char* path, char** strp)
{
    int i, len, res = 0;
    config_setting_t* child;
    char* subpath, *value, *old;
    const char* name;

    len = config_setting_length(parent);
    for (i = 0; i < len; i++) {
        child = config_setting_get_elem(parent, i);
        name = config_setting_name(child);
        if (!name) name = "";

        if(config_setting_is_list(parent) ||
           config_setting_is_array(parent)) {
            c2s_asprintf(&subpath, "%s[%d]%s", path, config_setting_index(child), name);
        } else {
            c2s_asprintf(&subpath, "%s/%s", path, name);
        }

        if (config_setting_is_scalar(child)) {
            scalar_to_string(&value, child);

            /* Add value to the output string  */
            if (*strp) {
                c2s_asprintf(&old, "%s", *strp);
                free(*strp);
            }  else {
                c2s_asprintf(&old, "%s", "");
            }
            c2s_asprintf(strp, "%s%s:%s", old, subpath, value);
            free(value);
            free(old);

            res++; /* At least one scalar was found */
        } else {
            /* It's an aggregate -- descend into it */
            res += cfg_as_string(child, subpath, strp);
        }

        free(subpath);
    }
    return res;
}


/* 0: success
   <0: error */
int sslhcfg_cl_parse(int argc, char* argv[], struct sslhcfg_item* cfg)
{
    int nerrors, res;
    config_t c;
    char* errmsg;
    config_setting_t* s;
    void* argtable[] = {
            #ifdef LIBCONFIG
        sslhcfg_conffile = arg_filen("F", "config", "<file>", 0, 1, "Specify configuration file"),
    #endif
         sslhcfg_verbose = arg_intn("v", "verbose", "<n>", 0, 1, "Override all verbosness options"),
         sslhcfg_verbose_config = arg_intn(NULL, "verbose-config", "<n>", 0, 1, "Print configuration at startup"),
         sslhcfg_verbose_config_error = arg_intn(NULL, "verbose-config-error", "<n>", 0, 1, "Print configuration errors"),
         sslhcfg_verbose_connections = arg_intn(NULL, "verbose-connections", "<n>", 0, 1, "Trace established incoming address to forward address"),
         sslhcfg_verbose_connections_try = arg_intn(NULL, "verbose-connections-try", "<n>", 0, 1, "Connection errors"),
         sslhcfg_verbose_connections_error = arg_intn(NULL, "verbose-connections-error", "<n>", 0, 1, "Connection attempts towards targets"),
         sslhcfg_verbose_fd = arg_intn(NULL, "verbose-fd", "<n>", 0, 1, "File descriptor activity, open/close/whatnot"),
         sslhcfg_verbose_packets = arg_intn(NULL, "verbose-packets", "<n>", 0, 1, "Hexdump packets on which probing is done"),
         sslhcfg_verbose_probe_info = arg_intn(NULL, "verbose-probe-info", "<n>", 0, 1, "Trace the probe process"),
         sslhcfg_verbose_probe_error = arg_intn(NULL, "verbose-probe-error", "<n>", 0, 1, "Failures and problems during probing"),
         sslhcfg_verbose_system_error = arg_intn(NULL, "verbose-system-error", "<n>", 0, 1, "System call failures"),
         sslhcfg_verbose_int_error = arg_intn(NULL, "verbose-int-error", "<n>", 0, 1, "Internal errors that should never happen"),
         sslhcfg_version = arg_litn("V", "version", 0, 1, "Print version information and exit"),
         sslhcfg_foreground = arg_litn("f", "foreground", 0, 1, "Run in foreground instead of as a daemon"),
         sslhcfg_inetd = arg_litn("i", "inetd", 0, 1, "Run in inetd mode: use stdin/stdout instead of network listen"),
         sslhcfg_numeric = arg_litn("n", "numeric", 0, 1, "Print IP addresses and ports as numbers"),
         sslhcfg_transparent = arg_litn(NULL, "transparent", 0, 1, "Set up as a transparent proxy"),
         sslhcfg_timeout = arg_intn("t", "timeout", "<n>", 0, 1, "Set up timeout before connecting to default target"),
         sslhcfg_udp_max_connections = arg_intn(NULL, "udp-max-connections", "<n>", 0, 1, "Number of concurrent UDP connections"),
         sslhcfg_user = arg_strn("u", "user", "<str>", 0, 1, "Username to change to after set-up"),
         sslhcfg_pidfile = arg_strn("P", "pidfile", "<file>", 0, 1, "Path to file to store PID of current instance"),
         sslhcfg_chroot = arg_strn("C", "chroot", "<path>", 0, 1, "Root to change to after set-up"),
         sslhcfg_syslog_facility = arg_strn(NULL, "syslog-facility", "<str>", 0, 1, "Facility to syslog to"),
         sslhcfg_logfile = arg_strn(NULL, "logfile", "<str>", 0, 1, "Log messages to a file"),
         sslhcfg_on_timeout = arg_strn(NULL, "on-timeout", "<str>", 0, 1, "Target to connect to when timing out"),
         sslhcfg_prefix = arg_strn(NULL, "prefix", "<str>", 0, 1, "Reserved for testing"),
 	sslhcfg_listen = arg_strn("p", "listen", "<host:port>", 0, 10, "Listen on host:port"),
 	sslhcfg_ssh = arg_strn(NULL, "ssh", "<host:port>", 0, 10, "Set up ssh target"),
 	sslhcfg_tls = arg_strn(NULL, "tls", "<host:port>", 0, 10, "Set up TLS/SSL target"),
 	sslhcfg_ssl = arg_strn(NULL, "ssl", "<host:port>", 0, 10, "Set up TLS/SSL target"),
 	sslhcfg_openvpn = arg_strn(NULL, "openvpn", "<host:port>", 0, 10, "Set up OpenVPN target"),
 	sslhcfg_tinc = arg_strn(NULL, "tinc", "<host:port>", 0, 10, "Set up tinc target"),
 	sslhcfg_wireguard = arg_strn(NULL, "wireguard", "<host:port>", 0, 10, "Set up WireGuard target"),
 	sslhcfg_xmpp = arg_strn(NULL, "xmpp", "<host:port>", 0, 10, "Set up XMPP target"),
 	sslhcfg_http = arg_strn(NULL, "http", "<host:port>", 0, 10, "Set up HTTP (plain) target"),
 	sslhcfg_adb = arg_strn(NULL, "adb", "<host:port>", 0, 10, "Set up ADB (Android Debug) target"),
 	sslhcfg_socks5 = arg_strn(NULL, "socks5", "<host:port>", 0, 10, "Set up socks5 target"),
 	sslhcfg_syslog = arg_strn(NULL, "syslog", "<host:port>", 0, 10, "Set up syslog target"),
 	sslhcfg_msrdp = arg_strn(NULL, "msrdp", "<host:port>", 0, 10, "Set up msrdp target"),
 	sslhcfg_anyprot = arg_strn(NULL, "anyprot", "<host:port>", 0, 10, "Set up default target"),
 	sslhcfg_end = arg_end(10)

    };

    /* Set up failure handler in case asprintf() runs out of
    * memory */
    ;
    if (setjmp(c2s_asprintf_fail)) {
        fprintf(stderr, "asprintf: probably out of memory\n");
        return -1;
    }

    /* Parse command line */
    nerrors = arg_parse(argc, argv, argtable);
    if (nerrors) {
        arg_print_errors(stdout, sslhcfg_end, "sslhcfg"); 
        arg_print_syntax(stdout, argtable, "\n");
        arg_print_glossary(stdout, argtable, "  %-25s\t%s\n");
        return -1;
    }


    config_init(&c);
    if (sslhcfg_conffile && sslhcfg_conffile->count) {
        if (!c2s_parse_file(sslhcfg_conffile->filename[0], &c, &errmsg)) {
            fprintf(stderr, "%s\n", errmsg);
            return -1;
        }
    }

    s = config_root_setting(&c);

    res = read_block(s, cfg, table_sslhcfg, &errmsg);
    if (!res) {
        fprintf(stderr, "%s\n", errmsg);
        return -1;
    }

    res = read_compounds(s, cfg, compound_cl_args, &errmsg);
    if (!res) {
        fprintf(stderr, "%s\n", errmsg);
        return -1;
    }

    errmsg = NULL;
    res = cfg_as_string(s, "", &errmsg);
    if (res)
        fprintf(stderr, "Unknown settings:\n%s\n", errmsg);

    return 0;
}


static void indent(FILE* out, int depth) 
{
    int i;
    for (i = 0; i < depth; i++)
        fprintf(out, "    ");
}

static void sslhcfg_protocols_fprint(
        FILE* out,
        struct sslhcfg_protocols_item* sslhcfg_protocols,
        int depth) 
{
    int i;
        indent(out, depth);
        fprintf(out, "name: %s", sslhcfg_protocols->name);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "host: %s", sslhcfg_protocols->host);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "port: %s", sslhcfg_protocols->port);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "service: %s", sslhcfg_protocols->service);
        if (! sslhcfg_protocols->service_is_present)
            fprintf(out, " <unset>");
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "is_unix: %d", sslhcfg_protocols->is_unix);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "is_udp: %d", sslhcfg_protocols->is_udp);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "udp_timeout: %d", sslhcfg_protocols->udp_timeout);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "fork: %d", sslhcfg_protocols->fork);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "tfo_ok: %d", sslhcfg_protocols->tfo_ok);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "transparent: %d", sslhcfg_protocols->transparent);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "resolve_on_forward: %d", sslhcfg_protocols->resolve_on_forward);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "log_level: %d", sslhcfg_protocols->log_level);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "keepalive: %d", sslhcfg_protocols->keepalive);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "sni_hostnames [%zu]:\n", sslhcfg_protocols->sni_hostnames_len);
        for (i = 0; i < sslhcfg_protocols->sni_hostnames_len; i++) {
            indent(out, depth+1);
            fprintf(out, "%d:\t%s\n", i, sslhcfg_protocols->sni_hostnames[i]);
        }
        indent(out, depth);
        fprintf(out, "alpn_protocols [%zu]:\n", sslhcfg_protocols->alpn_protocols_len);
        for (i = 0; i < sslhcfg_protocols->alpn_protocols_len; i++) {
            indent(out, depth+1);
            fprintf(out, "%d:\t%s\n", i, sslhcfg_protocols->alpn_protocols[i]);
        }
        indent(out, depth);
        fprintf(out, "regex_patterns [%zu]:\n", sslhcfg_protocols->regex_patterns_len);
        for (i = 0; i < sslhcfg_protocols->regex_patterns_len; i++) {
            indent(out, depth+1);
            fprintf(out, "%d:\t%s\n", i, sslhcfg_protocols->regex_patterns[i]);
        }
        indent(out, depth);
        fprintf(out, "minlength: %d", sslhcfg_protocols->minlength);
        if (! sslhcfg_protocols->minlength_is_present)
            fprintf(out, " <unset>");
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "proxyprotocol: %d", sslhcfg_protocols->proxyprotocol);
        if (! sslhcfg_protocols->proxyprotocol_is_present)
            fprintf(out, " <unset>");
        fprintf(out, "\n");
}

static void sslhcfg_listen_fprint(
        FILE* out,
        struct sslhcfg_listen_item* sslhcfg_listen,
        int depth) 
{
    
        indent(out, depth);
        fprintf(out, "host: %s", sslhcfg_listen->host);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "port: %s", sslhcfg_listen->port);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "is_udp: %d", sslhcfg_listen->is_udp);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "is_unix: %d", sslhcfg_listen->is_unix);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "keepalive: %d", sslhcfg_listen->keepalive);
        fprintf(out, "\n");
}

void sslhcfg_fprint(
        FILE* out,
        struct sslhcfg_item* sslhcfg,
        int depth) 
{
    int i;
        indent(out, depth);
        fprintf(out, "verbose: %d", sslhcfg->verbose);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "verbose_config: %d", sslhcfg->verbose_config);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "verbose_config_error: %d", sslhcfg->verbose_config_error);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "verbose_connections: %d", sslhcfg->verbose_connections);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "verbose_connections_try: %d", sslhcfg->verbose_connections_try);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "verbose_connections_error: %d", sslhcfg->verbose_connections_error);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "verbose_fd: %d", sslhcfg->verbose_fd);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "verbose_packets: %d", sslhcfg->verbose_packets);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "verbose_probe_info: %d", sslhcfg->verbose_probe_info);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "verbose_probe_error: %d", sslhcfg->verbose_probe_error);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "verbose_system_error: %d", sslhcfg->verbose_system_error);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "verbose_int_error: %d", sslhcfg->verbose_int_error);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "version: %d", sslhcfg->version);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "foreground: %d", sslhcfg->foreground);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "inetd: %d", sslhcfg->inetd);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "numeric: %d", sslhcfg->numeric);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "transparent: %d", sslhcfg->transparent);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "timeout: %d", sslhcfg->timeout);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "udp_max_connections: %d", sslhcfg->udp_max_connections);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "user: %s", sslhcfg->user);
        if (! sslhcfg->user_is_present)
            fprintf(out, " <unset>");
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "pidfile: %s", sslhcfg->pidfile);
        if (! sslhcfg->pidfile_is_present)
            fprintf(out, " <unset>");
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "chroot: %s", sslhcfg->chroot);
        if (! sslhcfg->chroot_is_present)
            fprintf(out, " <unset>");
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "syslog_facility: %s", sslhcfg->syslog_facility);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "logfile: %s", sslhcfg->logfile);
        if (! sslhcfg->logfile_is_present)
            fprintf(out, " <unset>");
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "on_timeout: %s", sslhcfg->on_timeout);
        fprintf(out, "\n");
        indent(out, depth);
        fprintf(out, "prefix: %s", sslhcfg->prefix);
        fprintf(out, "\n");

        indent(out, depth);
        fprintf(out, "listen [%zu]:\n", sslhcfg->listen_len);
        for (i = 0; i < sslhcfg->listen_len; i++) {
            sslhcfg_listen_fprint(out, &sslhcfg->listen[i], depth+1);
        }

        indent(out, depth);
        fprintf(out, "protocols [%zu]:\n", sslhcfg->protocols_len);
        for (i = 0; i < sslhcfg->protocols_len; i++) {
            sslhcfg_protocols_fprint(out, &sslhcfg->protocols[i], depth+1);
        }
}
