/*
 * Pound - the reverse-proxy load-balancer
 * Copyright (C) 2002-2020 Apsis GmbH
 *
 * This file is part of Pound.
 *
 * Pound is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Pound 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, see <http://www.gnu.org/licenses/> .
 *
 * Contact information:
 * Apsis GmbH
 * P.O.Box
 * 8707 Uetikon am See
 * Switzerland
 * EMail: roseg@apsis.ch
 */

#include    "pound.h"

static UT_array *ut_backends;
static UT_array *ut_http;

void
fatal(const char *fmt, ...)
{
    char    buf[MAXBUF + 1];
    va_list ap;

    buf[MAXBUF] = '\0';
    va_start(ap, fmt);
    vsnprintf(buf, MAXBUF, fmt, ap);
    va_end(ap);

    global.log_level = 1;
    logmsg(0, buf);
    exit(3);
}

static char *
file2str(const char *fname)
{
    char    *res;
    struct  stat st;
    FILE    *fin;

    if(stat(fname, &st)
    || (fin = fopen(fname, "r")) == NULL
    || (res = malloc(st.st_size + 1)) == NULL
    || fread(res, 1, st.st_size, fin) != st.st_size)
        fatal("YAML can't access file %s", fname);
    res[st.st_size] = '\0';
    fclose(fin);
    return res;
}

static void
get_global(yaml_document_t *document, yaml_node_t *root)
{
    yaml_node_pair_t    *cur_pair;
    yaml_node_t         *node;
    struct passwd       *pwent;
    struct group        *grent;

    logmsg(1, "start get_global %s:%d", __FILE__, __LINE__);
    for(cur_pair = root->data.mapping.pairs.start; cur_pair < root->data.mapping.pairs.top; cur_pair++)
        if(!strcasecmp("User", yaml_document_get_node(document, cur_pair->key)->data.scalar.value)) {
            if((pwent = getpwnam(yaml_document_get_node(document, cur_pair->value)->data.scalar.value)) == NULL)
                fatal("User %s not found", yaml_document_get_node(document, cur_pair->value)->data.scalar.value);
            global.user = pwent->pw_uid;
            logmsg(4, "user %d %s:%d", global.user, __FILE__, __LINE__);
        } else if(!strcasecmp("Group", yaml_document_get_node(document, cur_pair->key)->data.scalar.value)) {
            if((grent = getgrnam(yaml_document_get_node(document, cur_pair->value)->data.scalar.value)) == NULL)
                fatal("Group %s not found", yaml_document_get_node(document, cur_pair->value)->data.scalar.value);
            global.group = grent->gr_gid;
            logmsg(4, "group %d %s:%d", global.group, __FILE__, __LINE__);
        } else if(!strcasecmp("RootJail", yaml_document_get_node(document, cur_pair->key)->data.scalar.value)) {
            if((global.root_jail = strdup(yaml_document_get_node(document, cur_pair->value)->data.scalar.value)) == NULL)
                fatal("Out of memory at %ld", yaml_document_get_node(document, cur_pair->value)->start_mark.line);
            logmsg(4, "RootJail %s %s:%d", global.root_jail, __FILE__, __LINE__);
        } else if(!strcasecmp("Err404", yaml_document_get_node(document, cur_pair->key)->data.scalar.value)) {
            global.err404 = file2str(yaml_document_get_node(document, cur_pair->value)->data.scalar.value);
            logmsg(4, "err404 %s %s:%d", global.err404, __FILE__, __LINE__);
        } else if(!strcasecmp("Err405", yaml_document_get_node(document, cur_pair->key)->data.scalar.value)) {
            global.err405 = file2str(yaml_document_get_node(document, cur_pair->value)->data.scalar.value);
            logmsg(4, "err405 %s %s:%d", global.err405, __FILE__, __LINE__);
        } else if(!strcasecmp("Err500", yaml_document_get_node(document, cur_pair->key)->data.scalar.value)) {
            global.err500 = file2str(yaml_document_get_node(document, cur_pair->value)->data.scalar.value);
            logmsg(4, "err500 %s %s:%d", global.err500, __FILE__, __LINE__);
        } else
            fatal("Unknown directive %s at line %ld", yaml_document_get_node(document, cur_pair->key)->data.scalar.value,
                yaml_document_get_node(document, cur_pair->key)->start_mark.line);

    if(global.err404 == NULL)
        global.err404 = "<html><head><title>Not Found</title></head><body><h1>Page not found</h1></body></html>\r\n";
    if(global.err405 == NULL)
        global.err405 = "<html><head><title>Not Allowed</title></head><body><h1>Method not allowed</h1></body></html>\r\n";
    if(global.err500 == NULL)
        global.err500 = "<html><head><title>Error</title></head><body><h1>Internal error</h1></body></html>\r\n";

    return;
}

static void
get_backends(yaml_document_t *document, yaml_node_t *root)
{
    yaml_node_pair_t    *cur_pair;
    yaml_node_t         *vals;
    yaml_node_item_t    *cur;
    BACKEND             res;
    char                addr[NI_MAXHOST], port[NI_MAXSERV], redirect[NI_MAXHOST];
    struct addrinfo     hints;

    logmsg(1, "start get_backends %s:%d", __FILE__, __LINE__);
    memset(&hints, '\0', sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    for(cur = root->data.sequence.items.start; cur < root->data.sequence.items.top; cur++) {
        memset(&res, '\0', sizeof(res));
        res.id = *cur;
        vals = yaml_document_get_node(document, *cur);
        if(vals->type != YAML_MAPPING_NODE)
            fatal("Backend at %ld: not a mapping", vals->start_mark.line);
        addr[0] = '\0';
        port[0] = '\0';
        redirect[0] = '\0';
        for(cur_pair = vals->data.mapping.pairs.start; cur_pair < vals->data.mapping.pairs.top; cur_pair++)
            if(!strcasecmp("Address", yaml_document_get_node(document, cur_pair->key)->data.scalar.value)) {
                strncpy(addr, yaml_document_get_node(document, cur_pair->value)->data.scalar.value, NI_MAXHOST);
                logmsg(4, "addr %s %s:%d", addr, __FILE__, __LINE__);
            } else if(!strcasecmp("Port", yaml_document_get_node(document, cur_pair->key)->data.scalar.value)) {
                strncpy(port, yaml_document_get_node(document, cur_pair->value)->data.scalar.value, NI_MAXSERV);
                logmsg(4, "port %s %s:%d", port, __FILE__, __LINE__);
            } else if(!strcasecmp("Timeout", yaml_document_get_node(document, cur_pair->key)->data.scalar.value)) {
                res.timeout = atoi(yaml_document_get_node(document, cur_pair->value)->data.scalar.value);
                logmsg(4, "timeout %d %s:%d", res.timeout, __FILE__, __LINE__);
            } else if(!strcasecmp("Threads", yaml_document_get_node(document, cur_pair->key)->data.scalar.value)) {
                res.threads = atoi(yaml_document_get_node(document, cur_pair->value)->data.scalar.value);
                logmsg(4, "threads %d %s:%d", res.threads, __FILE__, __LINE__);
            } else if(!strcasecmp("HeadAdd", yaml_document_get_node(document, cur_pair->key)->data.scalar.value)) {
                if((res.add_header = strdup(yaml_document_get_node(document, cur_pair->value)->data.scalar.value)) == NULL)
                    fatal("HeadAdd out of memory at line %ld", yaml_document_get_node(document, cur_pair->key)->start_mark.line);
                if(strlen(res.add_header) == 0)
                    fatal("HeadAdd at line %ld: header may not be empty", yaml_document_get_node(document, cur_pair->key)->start_mark.line);
                logmsg(4, "HeadAdd %s %s:%d", res.add_header, __FILE__, __LINE__);
            } else
                fatal("Unknown directive %s at line %ld", yaml_document_get_node(document, cur_pair->key)->data.scalar.value,
                    yaml_document_get_node(document, cur_pair->key)->start_mark.line);
            
        if(addr[0]) {
            if(res.threads <= 0)
                res.threads = 8;
            if(res.timeout <= 0)
                res.timeout = 15;
            if(getaddrinfo(addr, port, &hints, &res.addr))
                fatal("Backend at %ld: unknown server %s:%s", vals->start_mark.line, addr, port);
        } else
            fatal("Backend at %ld: missing data", vals->start_mark.line);
        logmsg(2, "push %s:%d", __FILE__, __LINE__);
        utarray_push_back(ut_backends, &res);
    }
}

static UT_array *
get_svc_backends(yaml_document_t *document, yaml_node_t *root)
{
    yaml_node_item_t    *cur;
    UT_array            *res;
    UT_icd backend_icd = {sizeof(BACKEND *), NULL, NULL, NULL };
    int                 i;
    BACKEND             *be;

    if(root->type != YAML_SEQUENCE_NODE)
        fatal("YAML Services not sequence (%ld)", root->start_mark.line);
    utarray_new(res, &backend_icd);
    for(cur = root->data.sequence.items.start; cur < root->data.sequence.items.top; cur++) {
        for(i = 0; i < backends_len; i++)
            if(backends[i].id == *cur) {
                be = &backends[i];
                utarray_push_back(res, &be);
                break;
            }
    }
    return res;
}

static UT_array *
get_services(yaml_document_t *document, yaml_node_t *root)
{
    yaml_node_pair_t    *map_pair;
    yaml_node_item_t    *cur;
    yaml_node_t         *vals;
    UT_array            *res, *bes;
    BACKEND             *be;
    UT_icd service_icd = {sizeof(SERVICE), NULL, NULL, NULL };
    SERVICE             *svc;
    int                 i;
    char                pat[MAXBUF];

    logmsg(1, "start get_services %s:%d", __FILE__, __LINE__);
    if(root->type != YAML_SEQUENCE_NODE)
        fatal("YAML Services not sequence (%ld)", root->start_mark.line);
    utarray_new(res, &service_icd);
    bes = NULL;

    for(cur = root->data.sequence.items.start; cur < root->data.sequence.items.top; cur++) {
        vals = yaml_document_get_node(document, *cur);
        if(vals->type != YAML_MAPPING_NODE)
            fatal("Service at %ld: not a mapping", vals->start_mark.line);
        if((svc = calloc(1, sizeof(SERVICE))) == NULL)
            fatal("Service at %ld: out of memory", vals->start_mark.line);
        for(map_pair = vals->data.mapping.pairs.start; map_pair < vals->data.mapping.pairs.top; map_pair++) {
            if(!strcasecmp("URL", yaml_document_get_node(document, map_pair->key)->data.scalar.value)) {
                if((svc->url = malloc(sizeof(regex_t))) == NULL)
                    fatal("Service at %ld: out of memory", vals->start_mark.line);
                snprintf(pat, MAXBUF, "[^ \t]+[ \t]%s[ \t].*", yaml_document_get_node(document, map_pair->value)->data.scalar.value);
                if(regcomp(svc->url, pat, REG_ICASE))
                    fatal("Service at %ld: bad URL pattern %s", vals->start_mark.line, pat);
                logmsg(4, "URL %s %s:%d", yaml_document_get_node(document, map_pair->value)->data.scalar.value, __FILE__, __LINE__);
            } else if(!strcasecmp("Session", yaml_document_get_node(document, map_pair->key)->data.scalar.value)) {
                svc->session = atoi(yaml_document_get_node(document, map_pair->value)->data.scalar.value);
                logmsg(4, "session %d %s:%d", svc->session, __FILE__, __LINE__);
            } else if(!strcasecmp("HeadRequire", yaml_document_get_node(document, map_pair->key)->data.scalar.value)) {
                if((svc->head_require = malloc(sizeof(regex_t))) == NULL)
                    fatal("Service at %ld: out of memory", vals->start_mark.line);
                if(regcomp(svc->head_require, yaml_document_get_node(document, map_pair->value)->data.scalar.value, REG_ICASE))
                    fatal("Service at %ld: bad HeadRequire pattern %s", vals->start_mark.line, yaml_document_get_node(document, map_pair->value)->data.scalar.value);
                logmsg(4, "HeadRequire %s %s:%d", yaml_document_get_node(document, map_pair->value)->data.scalar.value, __FILE__, __LINE__);
            } else if(!strcasecmp("HeadDeny", yaml_document_get_node(document, map_pair->key)->data.scalar.value)) {
                if((svc->head_deny = malloc(sizeof(regex_t))) == NULL)
                    fatal("Service at %ld: out of memory", vals->start_mark.line);
                if(regcomp(svc->head_deny, yaml_document_get_node(document, map_pair->value)->data.scalar.value, REG_ICASE))
                    fatal("Service at %ld: bad HeadDeny pattern %s", vals->start_mark.line, yaml_document_get_node(document, map_pair->value)->data.scalar.value);
                logmsg(4, "HeadDeny %s %s:%d", yaml_document_get_node(document, map_pair->value)->data.scalar.value, __FILE__, __LINE__);
            } else if(!strcasecmp("Backends", yaml_document_get_node(document, map_pair->key)->data.scalar.value)) {
                bes = get_svc_backends(document, yaml_document_get_node(document, map_pair->value));
                if(utarray_len(bes) <= 0)
                    fatal("Service at %ld: no backends defined", vals->start_mark.line);
                svc->backends_len = utarray_len(bes);
                if((svc->backends = calloc(svc->backends_len, sizeof(BACKEND *))) == NULL)
                    fatal("Service at %ld: out of memory", vals->start_mark.line);
                for(i = 0; i < svc->backends_len; i++)
                    svc->backends[i] = *(BACKEND **)utarray_eltptr(bes, i);
                utarray_free(bes);
            } else
                fatal("Unknown directive %s at line %ld", yaml_document_get_node(document, map_pair->key)->data.scalar.value,
                    yaml_document_get_node(document, map_pair->key)->start_mark.line);
        }
        logmsg(2, "push %s:%d", __FILE__, __LINE__);
        utarray_push_back(res, &svc);
    }

    return res;
}

static void
get_http(yaml_document_t *document, yaml_node_t *root)
{
    yaml_node_pair_t    *map_pairs;
    yaml_node_item_t    *cur;
    yaml_node_t         *vals;
    HTTP_LISTENER       res;
    char                addr[NI_MAXHOST], port[NI_MAXSERV];
    struct addrinfo     hints;
    UT_array            *services;
    int                 i;

    logmsg(1, "start get_http %s:%d", __FILE__, __LINE__);
    memset(&hints, '\0', sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    memset(&res, '\0', sizeof(res));
    for(cur = root->data.sequence.items.start; cur < root->data.sequence.items.top; cur++) {
        vals = yaml_document_get_node(document, *cur);
        if(vals->type != YAML_MAPPING_NODE)
            fatal("HTTPListener at %ld: not a mapping", vals->start_mark.line);
        addr[0] ='\0';
        port[0] = '\0';
        for(map_pairs = vals->data.mapping.pairs.start; map_pairs < vals->data.mapping.pairs.top; map_pairs++)
            if(!strcasecmp("Address", yaml_document_get_node(document, map_pairs->key)->data.scalar.value)) {
                strncpy(addr, yaml_document_get_node(document, map_pairs->value)->data.scalar.value, NI_MAXHOST);
                logmsg(4, "addr %s %s:%d", addr, __FILE__, __LINE__);
            } else if(!strcasecmp("Port", yaml_document_get_node(document, map_pairs->key)->data.scalar.value)) {
                strncpy(port, yaml_document_get_node(document, map_pairs->value)->data.scalar.value, NI_MAXSERV);
                logmsg(4, "port %s %s:%d", port, __FILE__, __LINE__);
            } else if(!strcasecmp("Client", yaml_document_get_node(document, map_pairs->key)->data.scalar.value)) {
                res.client = atoi(yaml_document_get_node(document, map_pairs->value)->data.scalar.value);
                logmsg(4, "client %d %s:%d", res.client, __FILE__, __LINE__);
            } else if(!strcasecmp("Threads", yaml_document_get_node(document, map_pairs->key)->data.scalar.value)) {
                res.threads = atoi(yaml_document_get_node(document, map_pairs->value)->data.scalar.value);
                logmsg(4, "threads %d %s:%d", res.threads, __FILE__, __LINE__);
            } else if(!strcasecmp("Services", yaml_document_get_node(document, map_pairs->key)->data.scalar.value)) {
                services = get_services(document, yaml_document_get_node(document, map_pairs->value));
            } else
                fatal("Unknown directive %s at line %ld", yaml_document_get_node(document, map_pairs->key)->data.scalar.value,
                    yaml_document_get_node(document, map_pairs->key)->start_mark.line);
        if(res.threads <= 0)
            res.threads = 8;
        if(res.client <= 0)
            res.client = 5;
        if(getaddrinfo(addr, port, &hints, &res.addr))
            fatal("HTTPListener at %ld: unknown server %s:%s", vals->start_mark.line, addr, port);
        if(utarray_len(services) == 0)
            fatal("HTTPListener at %ld: no services defined", vals->start_mark.line);
        res.services_len = utarray_len(services);
        if((res.services = calloc(res.services_len, sizeof(SERVICE *))) == NULL)
            fatal("HTTPListener at %ld: out of memory", vals->start_mark.line);
        for(i = 0; i < res.services_len; i++)
            res.services[i] = *(SERVICE **)utarray_eltptr(services, i);
        utarray_free(services);
        logmsg(2, "push %s:%d", __FILE__, __LINE__);
        utarray_push_back(ut_http, &res);
    }
    return;    
}

static int *
get_ciphers(yaml_document_t *document, yaml_node_t *root)
{
    yaml_node_item_t    *cur;
    yaml_node_t         *vals;
    UT_array    *list;
    int         *res, c;

    logmsg(1, "start get_ciphers %s:%d", __FILE__, __LINE__);
    utarray_new(list, &ut_int_icd);
    if(root->type == YAML_SCALAR_NODE) {
        logmsg(4, "cipher %s %s:%d", root->data.scalar.value, __FILE__, __LINE__);
        if((c = mbedtls_ssl_get_ciphersuite_id(root->data.scalar.value)) <= 0)
            fatal("Unknown cipher %s", root->data.scalar.value);
        utarray_push_back(list, &c);
    } else if(root->type == YAML_SEQUENCE_NODE) {
        for(cur = root->data.sequence.items.start; cur < root->data.sequence.items.top; cur++) {
            vals = yaml_document_get_node(document, *cur);
            if(vals->type != YAML_SCALAR_NODE)
                fatal("Ciphers at %ld: not a sequence", vals->start_mark.line);
            logmsg(4, "cipher %s %s:%d", vals->data.scalar.value, __FILE__, __LINE__);
            if((c = mbedtls_ssl_get_ciphersuite_id(vals->data.scalar.value)) <= 0)
                fatal("Unknown cipher %s", vals->data.scalar.value);
            utarray_push_back(list, &c);
        }
    } else
        fatal("Syntax error at line %ld", root->start_mark.line);
    if((res = calloc(utarray_len(list) + 1, sizeof(int))) == NULL)
        fatal("Ciphers at %ld: out of memory", root->start_mark.line);
    for(c = 0; c < utarray_len(list); c++)
        res[c] = *((int *)utarray_eltptr(list, c));
    res[utarray_len(list)] = 0;
    utarray_free(list);
    return res;
}

static SNI *
get_one(char *filename)
{
    SNI         *res;
    mbedtls_x509_crt    *cur;
    mbedtls_x509_name   *nd;
    mbedtls_asn1_buf    data;
    mbedtls_x509_sequence   *san;
    regex_t     one_host;
    UT_array    *hosts;
    UT_icd      regex_icd = {sizeof(regex_t), NULL, NULL, NULL};
    char        buf[NI_MAXHOST];
    int         i, j;

    logmsg(1, "start get_one(%s) %s:%d", filename, __FILE__, __LINE__);
    if((res = malloc(sizeof(SNI))) == NULL)
        fatal("SNI: out of memory");
    mbedtls_x509_crt_init(&res->certificate);
    if(mbedtls_x509_crt_parse_file(&res->certificate, filename))
        fatal("SNI: can't read certificate %s", filename);
    mbedtls_pk_init(&res->key);
    if(mbedtls_pk_parse_keyfile(&res->key, filename, NULL))
        fatal("SNI: can't read key %s", filename);
    utarray_new(hosts, &regex_icd);
    for(cur = &res->certificate; cur != NULL; cur = cur->next) {
        if(mbedtls_pk_check_pair(&cur->pk, &res->key))
            continue;
        for(nd = &cur->subject; nd != NULL; nd = nd->next)
            if(MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &nd->oid) == 0) {
                data = nd->val;
                memset(buf, '\0', NI_MAXHOST);
                for(i = j = 0; i < data.len; i++)
                    if(data.p[i] == '*') {
                        buf[j++] = '[';
                        buf[j++] = '^';
                        buf[j++] = '.';
                        buf[j++] = ']';
                        buf[j++] = '+';
                    } else
                        buf[j++] = data.p[i];
                buf[j] = '\0';
                logmsg(4, "get_one add pattern %s %s:%d", buf, __FILE__, __LINE__);
                if(regcomp(&one_host, buf, REG_ICASE | REG_EXTENDED))
                    fatal("Certificate in %s: bad host name pattern %s",filename, buf);
                utarray_push_back(hosts, &one_host);
            }

        for(san = &cur->subject_alt_names; san != NULL; san = san->next)
            if(san->buf.tag == 130) {
                /* 130 seems to be the OID for DNS entries */
                data = san->buf;
                memset(buf, '\0', NI_MAXHOST);
                for(i = j = 0; i < data.len; i++)
                    if(data.p[i] == '*') {
                        buf[j++] = '[';
                        buf[j++] = '^';
                        buf[j++] = '.';
                        buf[j++] = ']';
                        buf[j++] = '+';
                    } else
                        buf[j++] = data.p[i];
                buf[j] = '\0';
                logmsg(4, "get_one add pattern %s %s:%d", buf, __FILE__, __LINE__);
                if(regcomp(&one_host, buf, REG_ICASE | REG_EXTENDED))
                    fatal("Certificate in %s: bad host alt name pattern %s",filename, buf);
                utarray_push_back(hosts, &one_host);
            }
        if((res->host = calloc(utarray_len(hosts), sizeof(regex_t))) == NULL)
            fatal("SNI: out of memory");
        res->host_len = utarray_len(hosts);
        for(i = 0; i < res->host_len; i++)
            res->host[i] = *((regex_t *)utarray_eltptr(hosts, i));
        utarray_free(hosts);
    }
    logmsg(4, "get_one: added %d patterns %s:%d", res->host_len, __FILE__, __LINE__);
    return res;
}

static SNI **
get_certificates(yaml_document_t *document, yaml_node_t *root)
{
    yaml_node_item_t    *cur;
    yaml_node_t         *vals;
    UT_array            *list;
    UT_icd              ut_sni_icd = {sizeof(SNI *), NULL, NULL, NULL};
    SNI                 **res;
    int                 i;
    SNI                 *sni;
    
    logmsg(1, "start get_certificates %s:%d", __FILE__, __LINE__);
    utarray_new(list, &ut_sni_icd);
    if(root->type == YAML_SCALAR_NODE) {
        sni = get_one(root->data.scalar.value);
        utarray_push_back(list, &sni);
    } else if(root->type == YAML_SEQUENCE_NODE) {
        for(cur = root->data.sequence.items.start; cur < root->data.sequence.items.top; cur++) {
            vals = yaml_document_get_node(document, *cur);
            if(vals->type != YAML_SCALAR_NODE)
                fatal("Certificates at %ld: not a file name", vals->start_mark.line);
            sni = get_one(vals->data.scalar.value);
            utarray_push_back(list, &sni);
        }
    } else
        fatal("Syntax error at line %ld", root->start_mark.line);
    if((res = calloc(utarray_len(list) + 1, sizeof(SNI *))) == NULL)
        fatal("SNI at %ld: out of memory", root->start_mark.line);
    for(i = 0; i < utarray_len(list); i++)
        res[i] = *(SNI **)utarray_eltptr(list, i);
    res[utarray_len(list)] = NULL;
    utarray_free(list);
    return res;
}

const static char *alpn_protocols[] = { "h2", "http/1.1", NULL};

static void
get_https(yaml_document_t *document, yaml_node_t *root)
{
    yaml_node_pair_t    *map_pairs;
    yaml_node_item_t    *cur;
    yaml_node_t         *vals;
    HTTP_LISTENER       res;
    char                addr[NI_MAXHOST], port[NI_MAXSERV];
    struct addrinfo     hints;
    UT_array            *services;
    int                 i, *ciphers;

    logmsg(1, "start get_https %s:%d", __FILE__, __LINE__);
    memset(&hints, '\0', sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    memset(&res, '\0', sizeof(res));
    if((res.conf = malloc(sizeof(mbedtls_ssl_config))) == NULL)
        fatal("HTTPSListener: out of memory");
    mbedtls_ssl_config_init(res.conf);
    mbedtls_ssl_conf_rng(res.conf, mbedtls_ctr_drbg_random, &tls_ctr_drbg);
    mbedtls_ssl_config_defaults(res.conf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
    mbedtls_ssl_conf_alpn_protocols(res.conf, alpn_protocols);
    mbedtls_ssl_conf_authmode(res.conf, MBEDTLS_SSL_VERIFY_NONE);
    for(cur = root->data.sequence.items.start; cur < root->data.sequence.items.top; cur++) {
        vals = yaml_document_get_node(document, *cur);
        if(vals->type != YAML_MAPPING_NODE)
            fatal("HTTPSListener at %ld: not a mapping", vals->start_mark.line);
        addr[0] = '\0';
        port[0] = '\0';
        for(map_pairs = vals->data.mapping.pairs.start; map_pairs < vals->data.mapping.pairs.top; map_pairs++)
            if(!strcasecmp("Address", yaml_document_get_node(document, map_pairs->key)->data.scalar.value)) {
                strncpy(addr, yaml_document_get_node(document, map_pairs->value)->data.scalar.value, NI_MAXHOST);
                logmsg(4, "address %s %s:%d", addr, __FILE__, __LINE__);
            } else if(!strcasecmp("Port", yaml_document_get_node(document, map_pairs->key)->data.scalar.value)) {
                strncpy(port, yaml_document_get_node(document, map_pairs->value)->data.scalar.value, NI_MAXSERV);
                logmsg(4, "port %s %s:%d", port, __FILE__, __LINE__);
            } else if(!strcasecmp("Client", yaml_document_get_node(document, map_pairs->key)->data.scalar.value)) {
                res.client = atoi(yaml_document_get_node(document, map_pairs->value)->data.scalar.value);
                logmsg(4, "client %d %s:%d", res.client, __FILE__, __LINE__);
            } else if(!strcasecmp("Threads", yaml_document_get_node(document, map_pairs->key)->data.scalar.value)) {
                res.threads = atoi(yaml_document_get_node(document, map_pairs->value)->data.scalar.value);
                logmsg(4, "threads %d %s:%d", res.threads, __FILE__, __LINE__);
            } else if(!strcasecmp("Services", yaml_document_get_node(document, map_pairs->key)->data.scalar.value)) {
                services = get_services(document, yaml_document_get_node(document, map_pairs->value));
            } else if(!strcasecmp("Certificates", yaml_document_get_node(document, map_pairs->key)->data.scalar.value)) {
                res.sni = get_certificates(document, yaml_document_get_node(document, map_pairs->value));
            } else if(!strcasecmp("Ciphers", yaml_document_get_node(document, map_pairs->key)->data.scalar.value)) {
                ciphers = get_ciphers(document, yaml_document_get_node(document, map_pairs->value));
            } else
                fatal("Unknown directive %s at line %ld", yaml_document_get_node(document, map_pairs->key)->data.scalar.value,
                    yaml_document_get_node(document, map_pairs->key)->start_mark.line);
        if(res.threads <= 0)
            res.threads = 8;
        if(res.client <= 0)
            res.client = 5;
        if(getaddrinfo(addr, port, &hints, &res.addr))
            fatal("HTTPSListener at %ld: unknown server %s:%s", vals->start_mark.line, addr, port);
        if(utarray_len(services) == 0)
            fatal("HTTPSListener at %ld: no services defined", vals->start_mark.line);
        res.services_len = utarray_len(services);
        if((res.services = calloc(res.services_len, sizeof(SERVICE *))) == NULL)
            fatal("HTTPSListener at %ld: out of memory", vals->start_mark.line);
        for(i = 0; i < res.services_len; i++)
            res.services[i] = *(SERVICE **)utarray_eltptr(services, i);
        utarray_free(services);
        if(ciphers != NULL)
            mbedtls_ssl_conf_ciphersuites(res.conf, ciphers);
        if(res.sni == NULL)
            fatal("HTTPSListener at %ld: no certificates defined", vals->start_mark.line);
        mbedtls_ssl_conf_sni(res.conf, do_sni, &res.sni);
        mbedtls_ssl_conf_own_cert(res.conf, &res.sni[0]->certificate, &res.sni[0]->key);
        if(res.sni[0]->certificate.next != NULL)
            mbedtls_ssl_conf_ca_chain(res.conf, res.sni[0]->certificate.next, NULL );
        mbedtls_ssl_conf_read_timeout(res.conf, (uint32_t)res.client * 1000);
        logmsg(2, "push %s:%d", __FILE__, __LINE__);
        utarray_push_back(ut_http, &res);
    }
    return;    
}

static void
get_others(yaml_document_t *document, yaml_node_t *root)
{
    yaml_node_pair_t    *cur_pair;
    yaml_node_t         *node;

    logmsg(1, "start get_others %s:%d", __FILE__, __LINE__);
    if(root->type != YAML_MAPPING_NODE)
        fatal("YAML root not mapping (%ld)", root->start_mark.line);

    for(cur_pair = root->data.mapping.pairs.start; cur_pair < root->data.mapping.pairs.top; cur_pair++) {
        node = yaml_document_get_node(document, cur_pair->key);
        if(node->type != YAML_SCALAR_NODE)
            fatal("YAML key not scalar (%ld)", node->start_mark.line);
        if(strcasecmp("Global", node->data.scalar.value)
        && strcasecmp("Backends", node->data.scalar.value)
        && strcasecmp("HTTPListeners", node->data.scalar.value)
        && strcasecmp("HTTPSListeners", node->data.scalar.value))
            fatal("YAML unknown directive %s (%ld)", node->data.scalar.value, node->start_mark.line);
    }

    return;
}

static yaml_node_t *
get_base(yaml_document_t *document, yaml_node_t *root, char *key, int type)
{
    yaml_node_pair_t    *cur_pair;
    yaml_node_t         *node;

    for(cur_pair = root->data.mapping.pairs.start; cur_pair < root->data.mapping.pairs.top; cur_pair++) {
        node = yaml_document_get_node(document, cur_pair->key);
        if(!strcasecmp(key, node->data.scalar.value)) {
            node = yaml_document_get_node(document, cur_pair->value);
            if(node->type != type)
                return NULL;
            return node;
        }
    }
    return NULL;
}
void
config(const int argc, char **argv)
{
    int                 c_opt, o_check = 0, o_version = 0, is_key, i;
    char                *f_conf = F_CONF;
    FILE                *f_in;
    yaml_parser_t       parser;
    yaml_document_t     document;
    yaml_node_t         *root, *sub;
    UT_icd backend_icd = {sizeof(BACKEND), NULL, NULL, NULL };
    BACKEND             *be;
    UT_icd http_icd = {sizeof(HTTP_LISTENER), NULL, NULL, NULL };
    HTTP_LISTENER       *http;

    memset(&global, '\0', sizeof(global));
    opterr = 0;
    global.pid = "/var/run/pound.pid";
    global.log_level = 0;
    while((c_opt = getopt(argc, argv, "f:cvd:p:")) > 0)
        switch(c_opt) {
        case 'f':
            /* configuration file specified on the commend line */
            f_conf = optarg;
            logmsg(4, "config file option %s %s:%d", f_conf, __FILE__, __LINE__);
            break;
        case 'p':
            /* configuration file specified on the commend line */
            global.pid = optarg;
            logmsg(4, "pid file option %s %s:%d", global.pid, __FILE__, __LINE__);
            break;
        case 'd':
            /* debug mode: run in foreground, messages to stdout */
            global.log_level = atoi(optarg);
            logmsg(4, "debug option %d %s:%d", global.log_level, __FILE__, __LINE__);
            break;
        case 'c':
            /* check only: parse configuration and exit */
            o_check = 1;
            break;
        case 'v':
            /* print version and exit */
            o_version = 1;
            break;
        default:
            global.log_level = 1;
            logmsg(0, "Unknown flag %c", c_opt);
            exit(1);
        }
    if(o_version) {
        global.log_level = 1;
        logmsg(0, "Pound version %s", VERSION);
        exit(0);
    }

    if((f_in = fopen(f_conf, "r")) == NULL)
        fatal("File %s not found", f_conf);
    if(!yaml_parser_initialize(&parser))
        fatal("Cannot initialise parser");
    yaml_parser_set_input_file(&parser, f_in);

    if(!yaml_parser_load(&parser, &document))
        fatal("Can't load configuration - YAML error \"%s\" at line %ld", parser.problem, parser.problem_mark.line);
    yaml_parser_delete(&parser);
    fclose(f_in);

    if((root = yaml_document_get_root_node(&document)) == NULL)
        fatal("YAML can't get root");

    utarray_new(ut_backends, &backend_icd);
    utarray_new(ut_http, &http_icd);

    get_others(&document, root);
    if((sub = get_base(&document, root, "Global", YAML_MAPPING_NODE)) != NULL)
        get_global(&document, sub);
    if((sub = get_base(&document, root, "Backends", YAML_SEQUENCE_NODE)) == NULL)
        fatal("Missing Backends section");
    get_backends(&document, sub);

    backends_len = utarray_len(ut_backends);
    if(backends_len == 0)
        fatal("No backends defined");
    if((backends = calloc(backends_len, sizeof(BACKEND))) == NULL)
        fatal("Backends: out of memory");
    for(i = 0, be = utarray_front(ut_backends); be != NULL; i++, be = utarray_next(ut_backends, be))
        memcpy(&backends[i], be, sizeof(BACKEND));
    utarray_free(ut_backends);

    if(get_base(&document, root, "HTTPListeners", YAML_SCALAR_NODE) == NULL) {
        if((sub = get_base(&document, root, "HTTPListeners", YAML_SEQUENCE_NODE)) == NULL)
            fatal("Missing HTTPListeners section");
        get_http(&document, sub);
    }
    if(get_base(&document, root, "HTTPSListeners", YAML_SCALAR_NODE) == NULL) {
        if((sub = get_base(&document, root, "HTTPSListeners", YAML_SEQUENCE_NODE)) == NULL)
            fatal("Missing HTTPSListeners section");
        get_https(&document, sub);
    }

    http_len = utarray_len(ut_http);
    if(http_len > 0) {
        if((http_listeners = calloc(http_len, sizeof(HTTP_LISTENER))) == NULL)
            fatal("Listeners: out of memory");
        for(i = 0, http = utarray_front(ut_http); http != NULL; i++, http = utarray_next(ut_http, http))
            memcpy(&http_listeners[i], http, sizeof(HTTP_LISTENER));
    }
    utarray_free(ut_http);

    if(http_len == 0)
        fatal("No listeners defined");

    yaml_document_delete(&document);
    if(o_check)
        exit(0);
    return;
}