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

GLOBAL  global;

BACKEND *backends;
int     backends_len;

HTTP_LISTENER   *http_listeners;
int             http_len;

mbedtls_ctr_drbg_context    tls_ctr_drbg;

regex_t rex_Expect, rex_Response, rex_ContentLength, rex_Chunked, rex_Connection_Closed, rex_Connection_HTTP2, rex_Upgrade_HTTP2;

sem_t   sem_start;

int
main(const int argc, char **argv)
{
    int             i, j, n, s_in, s_listener;
    HTTP_LISTENER   **listeners;
    struct pollfd   *listener_poll;
    struct addrinfo *listener_addr;
    BACKEND         *be;
    char            name[NI_MAXHOST], port[NI_MAXSERV], *msg;
    pthread_t       thr;
    pthread_attr_t  attr;
    FILE            *f_pid;
    mbedtls_entropy_context     tls_entropy;

    memset(&global, 0, sizeof(global));
    global.log_facility = LOG_FACILITY;
    openlog("pound", LOG_CONS | LOG_NDELAY, global.log_facility);
    mbedtls_entropy_init(&tls_entropy);
    mbedtls_ctr_drbg_init(&tls_ctr_drbg);
    getentropy(name, 16);
    mbedtls_ctr_drbg_seed(&tls_ctr_drbg, mbedtls_entropy_func, &tls_entropy, (const unsigned char *)name, 16);
    hpack_init();
    config(argc, argv);

    /* These are handled elsewhere */
    (void)signal(SIGHUP, SIG_IGN);
    (void)signal(SIGPIPE, SIG_IGN);

    /* Common regex */
    regcomp(&rex_Response, "HTTP[^ \t]*[ \t]+([0-9]+).*", REG_ICASE | REG_EXTENDED);
    regcomp(&rex_Expect, "Expect:[ \t]*100-continue", REG_ICASE | REG_EXTENDED);
    regcomp(&rex_ContentLength, "Content-Length:[ \t]*([0-9]+)", REG_ICASE | REG_EXTENDED);
    regcomp(&rex_Chunked, "Transfer-Encoding:.*chunked", REG_ICASE | REG_EXTENDED | REG_NOSUB);
    regcomp(&rex_Connection_Closed, "Connection:.*close", REG_ICASE | REG_EXTENDED | REG_NOSUB);
    regcomp(&rex_Connection_HTTP2, "Connection:.*upgrade", REG_ICASE | REG_EXTENDED | REG_NOSUB);
    regcomp(&rex_Upgrade_HTTP2, "Upgrade:.*h2c", REG_ICASE | REG_EXTENDED | REG_NOSUB);

    /* preamble */
    global.http2_preamble[0] = "PRI * HTTP/2.0\r\n";
    global.http2_preamble[1] = "\r\n";
    global.http2_preamble[2] = "SM\r\n";
    global.http2_preamble[3] = "\r\n";
    global.http2_preamble[4] = NULL;

    /* Daemonize if needed */
    if(global.log_level == 0) {
        switch(fork()) {
            case 0:
                close(0);
                close(1);
                close(2);
                if(setsid() < 0) {
                    logmsg(0, "Failed setsid");
                    exit(1);
                }
                break;
            case -1:
                logmsg(0, "Can't fork");
                exit(1);
                break;
            default:
                exit(0);
        }
        switch(fork()) {
            case 0:
                break;
            case -1:
                logmsg(0, "Can't fork");
                exit(1);
                break;
            default:
                exit(0);
        }
    }

    if((f_pid = fopen(global.pid, "w")) == NULL)
        logmsg(0, "Can't open pid file %s", global.pid);
    else {
        fprintf(f_pid, "%d", getpid());
        fclose(f_pid);
    }

    /* chroot, set[ug]id */
    if(global.root_jail)
        if(chroot(global.root_jail)) {
            logmsg(0, "Can't chroot to %s", global.root_jail);
            exit(1);
        }
    if(global.group > 0)
        if(setgid(global.group)) {
            logmsg(0, "Can't setgid %d", global.group);
            exit(1);
        }
    if(global.user > 0)
        if(setuid(global.user)) {
            logmsg(0, "Can't setuid %d", global.user);
            exit(1);
        }

    /* prepare threads */
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    /* resurrector */
    if(pthread_create(&thr, &attr, thr_resurrect, NULL)) {
        logmsg(0, "Resurrector: can't start thread");
        exit(1);
    }

    logmsg(2, "Prepare backends %s:%d", __FILE__, __LINE__);
    /* prepare back-ends */
    for(i = 0; i < backends_len; i++) {
        snprintf(name, NI_MAXHOST, "inproc://BE_%d", i);
        if((backends[i].sock = nn_socket(AF_SP, NN_PULL)) < 0) {
            logmsg(0, "Backend %d: can't create queue socket", i);
            exit(1);
        }
        if(nn_bind(backends[i].sock, name) < 0) {
            logmsg(0, "Backend %d: can't bind queue socket", i);
            exit(1);
        }
        if((backends[i].sock_in = nn_socket(AF_SP, NN_PUSH)) < 0) {
            logmsg(0, "Backend %d: can't create queue socket", i);
            exit(1);
        }
        if(nn_connect(backends[i].sock_in, name) < 0) {
            logmsg(0, "Backend %d: can't connect queue socket", i);
            exit(1);
        }
        sem_init(&sem_start, 0, 0);
        for(j = 0; j < backends[i].threads; j++)
            if(pthread_create(&thr, &attr, thr_backend, (void *)&backends[i])) {
                logmsg(0, "Backend %d: can't start threads %d", i);
                exit(1);
            }
        for(j = 0; j < backends[i].threads; j++)
            sem_wait(&sem_start);
        sem_destroy(&sem_start);
    }

    /* prepare listeners */
    logmsg(2, "Prepare listeners %s:%d", __FILE__, __LINE__);
    for(i = 0, n = 0; i < http_len; i++) {
        /* prepare the services */
        logmsg(3, "Prepare services for listener %d %s:%d", i, __FILE__, __LINE__);
        for(j = 0; j < http_listeners[i].services_len; j++) {
            snprintf(name, NI_MAXHOST, "inproc://SVC_%d_%d", i, j);
            if((http_listeners[i].services[j]->sock = nn_socket(AF_SP, NN_REP)) < 0) {
                logmsg(0, "Service %d/%d: can't create queue socket", i, j);
                exit(1);
            }
            if(nn_bind(http_listeners[i].services[j]->sock, name) < 0) {
                logmsg(0, "Service %d/%d: can't bind queue socket", i, j);
                exit(1);
            }
            if((http_listeners[i].services[j]->sock_in = nn_socket(AF_SP, NN_REQ)) < 0) {
                logmsg(0, "Service %d/%d: can't create queue socket", i, j);
                exit(1);
            }
            if(nn_connect(http_listeners[i].services[j]->sock_in, name) < 0) {
                logmsg(0, "Service %d/%d: can't connect queue socket", i, j);
                exit(1);
            }
            sem_init(&sem_start, 0, 0);
            if(pthread_create(&thr, &attr, thr_service, (void *)http_listeners[i].services[j])) {
                logmsg(0, "Service %d/%d: can't start thread", i, j);
                exit(1);
            }
            sem_wait(&sem_start);
            sem_destroy(&sem_start);
        }
        /* prepare the listeners */
        snprintf(name, NI_MAXHOST, "inproc://HTTP%d", i);
        if((http_listeners[i].sock_fan = nn_socket(AF_SP, NN_PULL)) < 0) {
            logmsg(0, "Listener %d: can't create fan socket", i);
            exit(1);
        }
        if(nn_bind(http_listeners[i].sock_fan, name) < 0) {
            logmsg(0, "Listener %d: can't bind fan socket", i);
            exit(1);
        }
        if((http_listeners[i].sock_in = nn_socket(AF_SP, NN_PUSH)) < 0) {
            logmsg(0, "Listener %d: can't create in socket", i);
            exit(1);
        }
        if(nn_connect(http_listeners[i].sock_in, name) < 0) {
            logmsg(0, "Listener %d: can't connect in socket", i);
            exit(1);
        }
        sem_init(&sem_start, 0, 0);
        for(j = 0; j < http_listeners[i].threads; j++)
            if(pthread_create(&thr, &attr, thr_http, (void *)&http_listeners[i])) {
                logmsg(0, "Http %d: can't start listener threads %d", i, j);
                exit(1);
            }
        for(j = 0; j < http_listeners[i].threads; j++)
            sem_wait(&sem_start);
        sem_destroy(&sem_start);
        for(listener_addr = http_listeners[i].addr; listener_addr != NULL; listener_addr = listener_addr->ai_next)
            n++;
    }

    if((listeners = calloc(n, sizeof(HTTP_LISTENER *))) == NULL || (listener_poll = calloc(n, sizeof(struct pollfd))) == NULL) {
            logmsg(0, "Listener poll: out of memory");
            exit(1);
    }
    for(i = 0, n = 0; i < http_len; i++)
        for(listener_addr = http_listeners[i].addr; listener_addr != NULL; listener_addr = listener_addr->ai_next) {
            getnameinfo(listener_addr->ai_addr, listener_addr->ai_addrlen, name, NI_MAXHOST, port, NI_MAXSERV, NI_NUMERICHOST);
            if((listener_poll[n].fd = socket(listener_addr->ai_family, SOCK_STREAM, 0)) < 0) {
                logmsg(0, "Listener %s:%s: can't open socket", name, port);
                continue;
            }
            if(bind(listener_poll[n].fd, listener_addr->ai_addr, listener_addr->ai_addrlen)) {
                close(listener_poll[n].fd);
                logmsg(0, "Listener %s:%s: can't bind socket", name, port);
                continue;
            }
            if(listen(listener_poll[n].fd, http_listeners[i].threads * 2)) {
                close(listener_poll[n].fd);
                logmsg(0, "Listener %s:%s: can't listen to socket", name, port);
                continue;
            }
            listener_poll[n].events = POLLIN;
            listeners[n++] = &http_listeners[i];
        }
    
    for(;;) {
        poll(listener_poll, n, -1);
        for(i = 0; i < n; i++)
            if(listener_poll[i].revents & POLLIN) {
                listener_poll[i].revents = 0;
                if((s_in = accept(listener_poll[i].fd, NULL, NULL)) < 0) {
                    logmsg(0, "Bad accept");
                    continue;
                }

                nn_send(listeners[i]->sock_in, &s_in, sizeof(s_in), 0);
            }
    }
}
