/*
 * 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"

void *
thr_service(void *arg)
{
    SERVICE *svc;
    struct SESSION {
        char            *addr;
        BACKEND         *be;
        time_t          last_access;
        UT_hash_handle  hh;
    } *sessions, *cur, *tmp;
    time_t  lastscan, now;
    struct nn_pollfd s_poll;
    char    *msg, addr[NI_MAXHOST];
    int     n, i;

    logmsg(1, "%lX start service %s:%d", pthread_self(), __FILE__, __LINE__);
    svc = (SERVICE *)arg;
    sessions = NULL;
    lastscan = time(NULL);
    sem_post(&sem_start);
    for(;;) {
        if(svc->session == 0) {
            logmsg(4, "%lX Null session: %s:%d", pthread_self(), __FILE__, __LINE__);
            /* accept a listener request, return an appropriate backend */
            if(nn_recv(svc->sock, &msg, NN_MSG, 0) <= 0) {
                logmsg(0, "Session: bad receive %s", nn_strerror(nn_errno()));
                continue;
            }
            nn_freemsg(msg);
            if(svc->backends_len > 1) {
                n = random();
                for(i = 0; i < svc->backends_len; i++) {
                    n = (n + i) % svc->backends_len;
                    if(!svc->backends[n]->is_dead)
                        break;
                }
            } else
                n = 0;
            if(!svc->backends[n]->is_dead) {
                nn_send(svc->sock, &svc->backends[n], sizeof(svc->backends[n]), 0);
                logmsg(4, "%lX Null session returns %d %s:%d", pthread_self(), n, __FILE__, __LINE__);
            } else {
                nn_send(svc->sock, "", 0, 0);
                logmsg(4, "%lX Null session no backend %s:%d", pthread_self(), __FILE__, __LINE__);
            }
        }
        now = time(NULL);
        if(now >= (lastscan + svc->session)) {
            /* prune expired sessions */
            HASH_ITER(hh, sessions, cur, tmp)
                if((cur->last_access + svc->session) < now) {
                    logmsg(4, "%lX prune %s %s:%d", pthread_self(), cur->addr, __FILE__, __LINE__);
                    HASH_DEL(sessions, cur);
                    free(cur->addr);
                    free(cur);
                }
            lastscan = time(NULL);
        }
        s_poll.fd = svc->sock;
        s_poll.events = NN_POLLIN;
        if(nn_poll(&s_poll, 1, (svc->session - (now - lastscan)) * 1000) != 1)
            continue;
        /* accept a listener request, return an appropriate backend */
        if(nn_recv(svc->sock, &msg, NN_MSG, 0) <= 0) {
            logmsg(0, "Session: bad receive %s", nn_strerror(nn_errno()));
            continue;
        }
        strcpy(addr, msg);
        nn_freemsg(msg);
        logmsg(4, "%lX find %s %s:%d", pthread_self(), addr, __FILE__, __LINE__);
        HASH_FIND_STR(sessions, msg, cur);
        if(cur && !cur->be->is_dead) {
            logmsg(4, "%lX found %s session %s:%d", pthread_self(), addr, __FILE__, __LINE__);
            nn_send(svc->sock, &cur->be, sizeof(cur->be), 0);
            cur->last_access = time(NULL);
            continue;
        }
        if(svc->backends_len > 1) {
            n = random();
            for(i = 0; i < svc->backends_len; i++) {
                n = (n + i) % svc->backends_len;
                if(!svc->backends[n]->is_dead)
                    break;
            }
        } else
            n = 0;
        if(!svc->backends[n]->is_dead) {
            if((cur = (struct SESSION *)malloc(sizeof(struct SESSION))) == NULL)
                logmsg(0, "Session: out of memory");
            else {
                cur->addr = strdup(addr);
                cur->be = svc->backends[i];
                cur->last_access = time(NULL);
                HASH_ADD_KEYPTR(hh, sessions, cur->addr, strlen(cur->addr), cur);
                logmsg(4, "%lX added %s %s:%d", pthread_self(), addr, __FILE__, __LINE__);
            }
            nn_send(svc->sock, &svc->backends[n], sizeof(svc->backends[n]), 0);
        } else {
            nn_send(svc->sock, "", 0, 0);
            logmsg(4, "%lX no backend %s:%d", pthread_self(), __FILE__, __LINE__);
        }
    }
}

int
get_be(HTTP_LISTENER *http, char *peer_name, char *request, char *headers[], struct hpack_headerblock *h2)
{
    int i, j, found, s_private;
    char    *msg, private_name[NI_MAXHOST], buf[MAXBUF], *method, *path;
    BACKEND *be;
    struct nn_pollfd    private_poll;
    struct hpack_header *header;

    logmsg(1, "%lX start get_be %s:%d", pthread_self(), __FILE__, __LINE__);
    if(request != NULL) {
        logmsg(2, "%lX get_be HTTP/1.1 %s:%d", pthread_self(), __FILE__, __LINE__);
        for(i = 0; i < http->services_len; i++) {
            if(http->services[i]->url != NULL && regexec(http->services[i]->url, request, 0, NULL, REG_ICASE))
                continue;
            found = (http->services[i]->head_require == NULL);
            for(j = 0; j < MAXHEADERS; j++) {
                if(headers[j] == NULL)
                    continue;
                if(http->services[i]->head_deny != NULL && !regexec(http->services[i]->head_deny, headers[j], 0, NULL, REG_ICASE)) {
                    found = 0;
                    break;
                }
                if(http->services[i]->head_require != NULL && !regexec(http->services[i]->head_require, headers[j], 0, NULL, REG_ICASE))
                    found = 1;
            }
            if(found)
                break;
        }
    } else {
        logmsg(2, "%lX get_be HTTP/2 %s:%d", pthread_self(), __FILE__, __LINE__);
        for(i = 0; i < http->services_len; i++) {
            found = (http->services[i]->head_require == NULL);
            method = path = NULL;
            TAILQ_FOREACH(header, h2, hdr_entry) {
                if(!strcasecmp(header->hdr_name, ":method"))
                    method = header->hdr_value;
                else if(!strcasecmp(header->hdr_name, ":path"))
                    path = header->hdr_value;
                else if(strcasecmp(header->hdr_name, ":scheme")) {
                    /* we don't care about :scheme */
                    if(!strcasecmp(header->hdr_name, ":authority"))
                        snprintf(buf, MAXBUF, "Host: %s\r\n", header->hdr_value);
                    else
                        snprintf(buf, MAXBUF, "%s: %s\r\n", header->hdr_name, header->hdr_value);
                    logmsg(4, "%lX check %d header %s %s:%d", pthread_self(), i, buf, __FILE__, __LINE__);
                    if(http->services[i]->head_require != NULL && !regexec(http->services[i]->head_require, buf, 0, NULL, REG_ICASE))
                        found = 1;
                    if(http->services[i]->head_deny != NULL && !regexec(http->services[i]->head_deny, buf, 0, NULL, REG_ICASE)) {
                        found = 0;
                        break;
                    }
                }
            }
            if(!found)
                continue;
            if(http->services[i]->url == NULL)
                break;
            snprintf(buf, MAXBUF, "%s %s HTTP/1.1\r\n", method, path);
            logmsg(4, "%lX check %d request %s %s:%d", pthread_self(), i, buf, __FILE__, __LINE__);
            if(!regexec(http->services[i]->url, buf, 0, NULL, REG_ICASE))
                break;
        }
    }

    logmsg(2, "%lX %sfound %d %s:%d", pthread_self(), i >= http->services_len? "not ": "", i, __FILE__, __LINE__);
    if(i >= http->services_len)
        return -1;
    nn_send(http->services[i]->sock_in, peer_name, strlen(peer_name) + 1, 0);
    if(nn_recv(http->services[i]->sock_in, &msg, NN_MSG, 0) == sizeof(be))
        memcpy(&be, msg, sizeof(be));
    else
        be = NULL;
    nn_freemsg(msg);

    if(be == NULL || (s_private = nn_socket(AF_SP, NN_PAIR)) < 0)
        return -1;
        
    do {
        snprintf(private_name, NI_MAXHOST, "inproc://HTTP_%ld", random() % 10000);
    } while(nn_bind(s_private, private_name) < 0);

    if(nn_send(be->sock_in, private_name, strlen(private_name) + 1, 0) < 0) {
        nn_close(s_private);
        return -1;
    }

    private_poll.fd = s_private;
    private_poll.events = NN_POLLOUT;
    if(nn_poll(&private_poll, 1, http->client * 1000) != 1) {
        nn_close(s_private);
        return -1;
    }

    logmsg(2, "%lX done get_be %s:%d", pthread_self(), __FILE__, __LINE__);
    return s_private;
}

static int
put_err(FILE *f_client, int code, char *reason, char *body)
{
    static char *fmt_body = "HTTP/1.1 %d %s\r\nContent-length: %d\r\n\r\n";
    static char *fmt_empty = "HTTP/1.1 %d %s\r\r\n";

    if(body != NULL) {
        fprintf(f_client, fmt_body, code, reason, body);
        return strlen(fmt_body) + 1 + strlen(reason) + strlen(body);
    } else {
        fprintf(f_client, fmt_empty, code, reason);
        return strlen(fmt_empty) + 1 + strlen(reason);
    }
}

static void
do_request(HTTP_LISTENER *http, FILE *f_client, char *peer_name, char *crt_buf)
{
    BACKEND         *be;
    char            *msg, *headers[MAXHEADERS], private_name[NI_MAXHOST], request[MAXBUF], buf[MAXBUF];
    int             i, is_closed, close_at_end, upgrade_h2, is_chunked, is_expect, s_private, header_found;
    long            content_length;
    struct timespec t_wait;
    regmatch_t      match[2];

    logmsg(1, "%lX start do_request %s:%d", pthread_self(), __FILE__, __LINE__);
    for(;;) {
        logmsg(2, "%lX start loop %s:%d", pthread_self(), __FILE__, __LINE__);
        is_closed = 0;
        close_at_end = 0;
        content_length = 0L;
        is_chunked = 0;
        is_expect = 0;
        upgrade_h2 = 0;
        for(i = 0; i < MAXHEADERS; i++)
            headers[i] = NULL;
        while(!(is_closed = (fgets(buf, MAXBUF - 1, f_client) == NULL))) {
            if(buf[0] != '\r' && buf[0] != '\n')
                break;
        }
        if(is_closed) {
            logmsg(2, "%lX client closed %s:%d", pthread_self(), __FILE__, __LINE__);
            return;
        }
        strcpy(request, buf);
        if(!strncasecmp(request, "CONNECT", strlen("CONNECT"))) {
            /* CONNECT is the only refused HTTP request type */
            time_stamp(buf);
            logmsg(0, "%s - - [%s] \"%s\" 405 %d CONNECT not allowed", peer_name, buf, request, put_err(f_client, 405, "CONNECT not allowed", global.err405));
            return;
        }
        logmsg(4, "%lX request %s %s:%d", pthread_self(), request, __FILE__, __LINE__);
        if(!strcasecmp(request, global.http2_preamble[0])) {
            /*  if the request looks like
                    PRI * HTTP/2.0\r\n
                followed by
                    \r\n
                    SM\r\n
                    \r\n
                then this is a direct HTTP/2 connection or a HTTP/2 over TLS connection

                so call do_http2() and return
            */
           for(i = 1; global.http2_preamble[i]; i++) {
                if(fgets(buf, MAXBUF - 1, f_client) == NULL) {
                    time_stamp(buf);
                    logmsg(0, "%s - - [%s] \"%s\" 405 %d HTTP/2 preamble premature EOF", peer_name, buf, request, put_err(f_client, 405, "HTTP/2 preamble premature EOF", global.err405));
                    return;
                }
                logmsg(4, "%lX preamble %d -> %s %s:%d", pthread_self(), i, buf, __FILE__, __LINE__);
                if(strcasecmp(buf, global.http2_preamble[i])) {
                    fwrite(global.err405, 1, strlen(global.err405), f_client);
                    time_stamp(buf);
                    logmsg(0, "%s - - [%s] \"%s\" 405 %d HTTP/2 partial preamble", peer_name, buf, request, put_err(f_client, 405, "HTTP/2 partial preamble", global.err405));
                    return;
                }
           }
           do_http2(http, f_client, peer_name, crt_buf, upgrade_h2);
           return;
        }
        for(i = 0; i < MAXHEADERS && !(is_closed = ((fgets(buf, MAXBUF - 1, f_client) == NULL))); )  {
            logmsg(4, "%lX header %s %s:%d", pthread_self(), buf, __FILE__, __LINE__);
            if(buf[0] == '\r' || buf[0] == '\n')
                break;
            if(!strncasecmp(buf, "Upgrade:", strlen("Upgrade:"))) {
                /* Upgrade: h2c ==> next request will be HTTP2 */
                upgrade_h2 = upgrade_h2 || !regexec(&rex_Upgrade_HTTP2, buf, 0, NULL, 0);
                continue;
            } else if(!strncasecmp(buf, "Connection:", strlen("Connection:"))) {
                /*  Connection: Upgrade in conjunction with Upgrade: h2c ==> next request will be HTTP2
                    Response will be:
                        Connection: Upgrade
                        Upgrade: h2c
                    so call do_http2() on receipt of the preamble
                    do_http2() will issue a RST_STREAM/REFUSED_STREAM on stream 1 so the client will re-issue the request in HTTP/2
                */
                upgrade_h2 = upgrade_h2 || !regexec(&rex_Connection_HTTP2, buf, 0, NULL, 0);
                close_at_end = !regexec(&rex_Connection_Closed, buf, 0, NULL, 0);
                logmsg(3, "%lX upgrade_h2 %d close_at_end %d %s:%d", pthread_self(), upgrade_h2, close_at_end, __FILE__, __LINE__);
                continue;
            } else if(!regexec(&rex_ContentLength, buf, 2, match, REG_ICASE))
                sscanf(buf + match[1].rm_so, "%ld", &content_length);
            else if(!regexec(&rex_Chunked, buf, 0, NULL, REG_ICASE))
                is_chunked = 1;
            else
                is_expect = !regexec(&rex_Expect, buf, 0, NULL, REG_ICASE);
            if((headers[i++] = strdup(buf)) == NULL) {
                logmsg(0, "Out of memory");
                time_stamp(buf);
                logmsg(0, "%s - - [%s] \"%s\" 500 %d Out of memory", peer_name, buf, request, put_err(f_client, 500, "Out of memory", global.err500));
                is_closed = 1;
                break;
            }
        }
        logmsg(3, "%lX content_length %d is_chunked %d is_expect %d %s:%d", pthread_self(), content_length, is_chunked, is_expect, __FILE__, __LINE__);
        if(is_closed) {
            for(i = 0; i < MAXHEADERS; i++)
                if(headers[i])
                    free(headers[i]);
            return;
        }
        if(content_length > 0L && is_chunked) {
            time_stamp(buf);
            logmsg(0, "%s - - [%s] \"%s\" 405 %d Chunked and Content-length", peer_name, buf, request, put_err(f_client, 405, "Chunked and Content-length", global.err405));
            for(i = 0; i < MAXHEADERS; i++)
                if(headers[i])
                    free(headers[i]);
            return;
        }
        if(is_expect)
            put_err(f_client, 100, "Continue", NULL);
        
        if(upgrade_h2) {
            logmsg(2, "%lX upgrade HTTP/2 consume request %s:%d", pthread_self(), __FILE__, __LINE__);
            if(content_length > 0L)
                while(content_length > 0L) {
                    i = fread(buf, sizeof(char), content_length >= MAXBUF? MAXBUF - 1: content_length, f_client);
                    if(i <= 0)
                        content_length = -1L;
                }
            else if(is_chunked)
                while(fgets(buf, MAXBUF - 1, f_client) != NULL) {
                    sscanf(buf, "%ld", &content_length);
                    if(content_length > 0L) {
                        while(content_length > 0L) {
                            i = fread(buf, sizeof(char), content_length >= MAXBUF? MAXBUF - 1: content_length, f_client);
                            if(i <= 0)
                                content_length = -1L;
                        }
                        fgets(buf, MAXBUF, f_client);
                    } else {
                        while(fgets(buf, MAXBUF, f_client) != NULL) {
                            if(buf[0] == '\r' || buf[0] == '\n')
                                break;
                        }
                    }
                }
            put_err(f_client, 101, "Switching protocols\r\nConnection: Upgrade\r\nUpgrade: h2c", NULL);
            continue;
        }

        if((s_private = get_be(http, peer_name, request, headers, NULL)) < 0) {
            time_stamp(buf);
            logmsg(0, "%s - - [%s] \"%s\" 500 %d No backend", peer_name, buf, request, put_err(f_client, 500, "No backend", global.err500));
            return;
        }

        logmsg(2, "%lX got backend %s:%d", pthread_self(), __FILE__, __LINE__);
        i = 1;
        nn_send(s_private, &i, sizeof(int), 0);
        nn_send(s_private, peer_name, strlen(peer_name) + 1, 0);
        nn_send(s_private, request, strlen(request) + 1, 0);
        if(crt_buf[0]) {
            logmsg(3, "%lX send cert %s:%d", pthread_self(), __FILE__, __LINE__);
            nn_send(s_private, "X-Pound-Cert: ", 15, 0);
            nn_send(s_private, crt_buf, strlen(crt_buf) + 1, 0);
            nn_send(s_private, "\r\n", 3, 0);
        }
        logmsg(3, "%lX send headers %s:%d", pthread_self(), __FILE__, __LINE__);
        for(i = 0; i < MAXHEADERS; i++)
            if(headers[i]) {
                nn_send(s_private, headers[i], strlen(headers[i]) + 1, 0);
                free(headers[i]);
            }
        nn_send(s_private, "\r\n", 3, 0);

        if(content_length > 0L) {
            logmsg(3, "%lX send content_length %s:%d", pthread_self(), __FILE__, __LINE__);
            while(content_length > 0L) {
                memset(buf, '\0', MAXBUF);
                i = fread(buf, sizeof(char), content_length >= MAXBUF? MAXBUF - 1: content_length, f_client);
                if(i > 0) {
                    nn_send(s_private, buf, i, 0);
                    content_length -= i;
                } else
                    content_length = -1L;
            }
        } else if(is_chunked) {
            logmsg(3, "%lX send is_chunked %s:%d", pthread_self(), __FILE__, __LINE__);
            while(fgets(buf, MAXBUF - 1, f_client) != NULL) {
                sscanf(buf, "%ld", &content_length);
                if(content_length > 0L) {
                    while(content_length > 0L) {
                        memset(buf, '\0', MAXBUF);
                        i = fread(buf, sizeof(char), content_length >= MAXBUF? MAXBUF - 1: content_length, f_client);
                        if(i > 0) {
                            nn_send(s_private, buf, i, 0);
                            content_length -= i;
                        } else
                            content_length = -1L;
                    }
                    if(fgets(buf, MAXBUF, f_client) != NULL)
                        nn_send(s_private, buf, strlen(buf) + 1, 0);
                } else {
                    while(fgets(buf, MAXBUF, f_client) != NULL) {
                        if(regexec(&rex_ContentLength, buf, 2, match, REG_ICASE))
                            nn_send(s_private, buf, strlen(buf) + 1, 0);
                        if(buf[0] == '\r' || buf[0] == '\n')
                            break;
                    }
                }
            }
        }
        nn_send(s_private, "", 0, 0);

        logmsg(2, "%lX pass response %s:%d", pthread_self(), __FILE__, __LINE__);
        while((i = nn_recv(s_private, &msg, NN_MSG, 0)) > 0) {
            fwrite(msg, i, 1, f_client);
            nn_freemsg(msg);
        }

        /* end of comms; has to be done from this side, as no LINGER available */
        nn_send(s_private, "", 0, 0);
        /* sleep to make sure all messages are through before closing the channel */
        t_wait.tv_sec = 0;
        t_wait.tv_nsec = 1000000;
        nanosleep(&t_wait, NULL);

        nn_close(s_private);
        logmsg(2, "%lX loop done %s:%d", pthread_self(), __FILE__, __LINE__);
        if(close_at_end)
            return ;
    }
}

typedef struct cookie {
    mbedtls_ssl_context *fd;
}   COOKIE;

static size_t
c_read(void *cv, char *buf, size_t size)
{
    COOKIE  *c;
    int     n;
    size_t  n_read;

    c = (COOKIE *)cv;
    n_read = 0;
    while(n_read < size && (n = mbedtls_ssl_read(c->fd, buf + n_read, size - n_read)) > 0)
        n_read += n;
    return n_read;
}

static size_t
c_write(void *cv, char *buf, size_t size)
{
    COOKIE  *c;

    c = (COOKIE *)cv;
    return mbedtls_ssl_write(c->fd, buf, size);
}

static int
c_close(void *cv)
{
    COOKIE  *c;
    int     res;
    mbedtls_net_context *ssl_fd;

    c = (COOKIE *)cv;
    res = mbedtls_ssl_close_notify(c->fd);
    ssl_fd = c->fd->p_bio;
    mbedtls_ssl_free(c->fd);
    mbedtls_net_free(ssl_fd);
    return res;
}

void *
thr_http(void *arg)
{
    HTTP_LISTENER   *http;
    BACKEND         *be;
    char            *msg, peer_name[NI_MAXHOST], crt_buf[MAXBUF];
    struct sockaddr peer_addr;
    int             sock_client, n;
    FILE            *f_client;
    struct linger   s_linger;
    struct timeval  s_time;
    mbedtls_ssl_context ssl;
    mbedtls_net_context ssl_client;
    COOKIE          c;
    cookie_io_functions_t   cio;

    logmsg(1, "%lX thr_http start %s:%d", pthread_self(), __FILE__, __LINE__);
    http = (HTTP_LISTENER *)arg;
    sem_post(&sem_start);
    for(;;) {
        logmsg(2, "%lX start loop %s:%d", pthread_self(), __FILE__, __LINE__);
        if(nn_recv(http->sock_fan, &msg, NN_MSG, 0) <= 0) {
            logmsg(0, "HTTP: bad receive %s", nn_strerror(nn_errno()));
            continue;
        }
        memcpy(&sock_client, msg, sizeof(sock_client));
        nn_freemsg(msg);
        n = sizeof(peer_addr);
        getpeername(sock_client, &peer_addr, &n);
        getnameinfo(&peer_addr, sizeof(peer_addr), peer_name, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
        logmsg(4, "%lX peer address %s %s:%d", pthread_self(), peer_name, __FILE__, __LINE__);
        if(http->conf == NULL) {
            s_linger.l_onoff = 1;
            s_linger.l_linger = 0;
            setsockopt(sock_client, SOL_SOCKET, SO_LINGER, &s_linger, sizeof(s_linger));
            s_time.tv_sec = http->client;
            s_time.tv_usec = 0;
            setsockopt(sock_client, SOL_SOCKET, SO_RCVTIMEO, &s_time, sizeof(s_time));
            setsockopt(sock_client, SOL_SOCKET, SO_SNDTIMEO, &s_time, sizeof(s_time));
            f_client = fdopen(sock_client, "r+");
        } else {
            mbedtls_net_init(&ssl_client);
            ssl_client.fd = sock_client;
            mbedtls_ssl_init(&ssl);
            mbedtls_ssl_setup(&ssl, http->conf);
            mbedtls_ssl_set_bio(&ssl, &sock_client, mbedtls_net_send, NULL, mbedtls_net_recv_timeout);
            if(n = mbedtls_ssl_handshake(&ssl)) {
                mbedtls_strerror(n, crt_buf, MAXBUF);
                logmsg(0, "Failed handshake from %s: %d - %s", peer_name, n, crt_buf);
                mbedtls_ssl_free(&ssl);
                close(sock_client);
                continue;
            }
            logmsg(2, "%lX handshake OK %s:%d", pthread_self(), __FILE__, __LINE__);
            if(mbedtls_ssl_get_peer_cert(&ssl) != NULL) {
                mbedtls_x509_crt_info(crt_buf, MAXBUF, "\t", mbedtls_ssl_get_peer_cert(&ssl));
                logmsg(4, "%lX peer certificate %s %s:%d", pthread_self(), crt_buf, __FILE__, __LINE__);
                for(n = 0; n < MAXBUF && crt_buf[n]; n++)
                    ;
                crt_buf[--n] = '\0';
            } else
                crt_buf[0] = '\0';
            /* for HTTP2: !strcmp(mbedtls_ssl_get_alpn_protocol(&ssl), "h2"), but we don't really need it */
            c.fd = &ssl;
            cio.read = (cookie_read_function_t *)c_read;
            cio.write = (cookie_write_function_t *)c_write;
            cio.seek = NULL;
            cio.close = (cookie_close_function_t *)c_close;

            if((f_client = fopencookie(&c, "w+", cio)) == NULL) {
                logmsg(0, "fopencookie failed");
                mbedtls_ssl_free(&ssl);
                mbedtls_net_free(&ssl_client);
                close(sock_client);
                continue;
            }
            setvbuf(f_client, NULL, _IONBF, 0);
        }
        do_request(http, f_client, peer_name, crt_buf);
        fclose(f_client);
        logmsg(2, "%lX done loop %s:%d", pthread_self(), __FILE__, __LINE__);
    }
}