#define _POSIX_C_SOURCE 200809L

#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#include "config.h"

typedef struct {
    char bind_ip[64];

    int ssl_enable;
    int http_port;
    int https_port;
    int daemonize;

    char cert_file[512];
    char key_file[512];
    char pid_file[512];
    char log_file[512];

    char smart_conf[512];
    char smart_perl_path[512];
} nw_config_t;

static FILE *g_log_fp = NULL;
static int g_log_level = LOG_LEVEL_DEFAULT;
static char g_log_path[512] = LOG_PATH_DEFAULT;

static volatile sig_atomic_t g_terminate = 0;
static volatile sig_atomic_t g_got_signal = 0;

static int g_pid_file_written = 0;
static char g_pid_file_path[512] = { 0 };

/* ------------------------------------------------------------ */
/* Console lifecycle output                                     */
/* ------------------------------------------------------------ */

static void console_msg(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);

    fputc('\n', stderr);
    fflush(stderr);
}

/* ------------------------------------------------------------ */
/* Logging                                                      */
/* ------------------------------------------------------------ */

static void log_open(void)
{
    if (!g_log_fp) {
        g_log_fp = fopen(g_log_path, "a");
        if (!g_log_fp) {
            g_log_fp = stderr;
        }
    }
}

static void log_reopen(const char *path)
{
    if (g_log_fp && g_log_fp != stderr) {
        fclose(g_log_fp);
    }
    g_log_fp = NULL;

    snprintf(g_log_path, sizeof(g_log_path), "%s", path);
    log_open();
}

static void log_msg(int level, const char *fmt, ...)
{
    time_t now;
    struct tm tm_now;
    char tbuf[64];
    const char *lvl = "INFO";
    va_list ap;

    if (level > g_log_level) {
        return;
    }

    log_open();

    now = time(NULL);
    localtime_r(&now, &tm_now);
    strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm_now);

    if (level == LOG_LEVEL_ERROR) {
        lvl = "ERROR";
    } else if (level == LOG_LEVEL_DEBUG) {
        lvl = "DEBUG";
    }

    fprintf(g_log_fp, "[%s] [%s] ", tbuf, lvl);

    va_start(ap, fmt);
    vfprintf(g_log_fp, fmt, ap);
    va_end(ap);

    fputc('\n', g_log_fp);
    fflush(g_log_fp);
}

static void die(const char *msg)
{
    log_msg(LOG_LEVEL_ERROR, "%s: %s", msg, strerror(errno));
    exit(EXIT_FAILURE);
}

static void ssl_die(const char *msg)
{
    log_msg(LOG_LEVEL_ERROR, "%s", msg);
    ERR_print_errors_fp(g_log_fp ? g_log_fp : stderr);
    exit(EXIT_FAILURE);
}

/* ------------------------------------------------------------ */
/* Helpers                                                      */
/* ------------------------------------------------------------ */

static void trim(char *s)
{
    char *p = s;
    size_t len;

    while (*p && isspace((unsigned char)*p)) {
        p++;
    }

    if (p != s) {
        memmove(s, p, strlen(p) + 1);
    }

    len = strlen(s);
    while (len > 0 && isspace((unsigned char)s[len - 1])) {
        s[len - 1] = '\0';
        len--;
    }
}

static void strip_quotes(char *s)
{
    size_t len = strlen(s);

    if (len >= 2) {
        if ((s[0] == '\'' && s[len - 1] == '\'') ||
            (s[0] == '"'  && s[len - 1] == '"')) {
            memmove(s, s + 1, len - 2);
            s[len - 2] = '\0';
        }
    }
}

static int parse_perl_assignment(const char *line, char *key, size_t ksz, char *val, size_t vsz)
{
    const char *p = line;
    size_t ki = 0;
    size_t vi = 0;

    while (*p && isspace((unsigned char)*p)) {
        p++;
    }
    if (*p != '$') {
        return 0;
    }
    p++;

    while (*p && (isalnum((unsigned char)*p) || *p == '_')) {
        if (ki + 1 < ksz) {
            key[ki++] = *p;
        }
        p++;
    }
    key[ki] = '\0';

    while (*p && isspace((unsigned char)*p)) {
        p++;
    }
    if (*p != '=') {
        return 0;
    }
    p++;

    while (*p && isspace((unsigned char)*p)) {
        p++;
    }

    while (*p && *p != ';' && *p != '\n' && *p != '\r') {
        if (vi + 1 < vsz) {
            val[vi++] = *p;
        }
        p++;
    }
    val[vi] = '\0';

    trim(key);
    trim(val);
    strip_quotes(val);

    return key[0] != '\0';
}

static int file_exists_and_executable(const char *path)
{
    return access(path, X_OK) == 0;
}

static int set_nonblocking(int fd)
{
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0) {
        return -1;
    }
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
        return -1;
    }
    return 0;
}

static void print_usage(const char *progname)
{
    fprintf(stdout,
        "%s %s\n"
        "\n"
        "Usage: %s [-h] [-d] [-s] [-c <smart.conf>] [-p <pidfile>] [-l <logfile>]\n"
        "\n"
        "Options:\n"
        "  -h, --help            Show this help text and exit\n"
        "  -d, --daemon          Run in daemon mode\n"
        "  -s, --stop            Stop the running nwwebui instance\n"
        "  -c, --config <file>   Use an alternate smart.conf path\n"
        "  -p, --pidfile <file>  Override PID file path\n"
        "  -l, --logfile <file>  Override log file path\n",
        NWWEBUI_NAME, NWWEBUI_VERSION, progname);
}

/* ------------------------------------------------------------ */
/* PID file handling                                            */
/* ------------------------------------------------------------ */

static void remove_pid_file(void)
{
    if (g_pid_file_written) {
        unlink(g_pid_file_path);
        g_pid_file_written = 0;
    }
}

static void write_pid_file(const char *path)
{
    FILE *fp = fopen(path, "w");
    if (!fp) {
        die("fopen pid file");
    }

    fprintf(fp, "%ld\n", (long)getpid());
    fclose(fp);

    snprintf(g_pid_file_path, sizeof(g_pid_file_path), "%s", path);
    g_pid_file_written = 1;
}

static pid_t read_pid_file(const char *path)
{
    FILE *fp;
    long pid = -1;

    fp = fopen(path, "r");
    if (!fp) {
        return -1;
    }

    if (fscanf(fp, "%ld", &pid) != 1) {
        fclose(fp);
        return -1;
    }

    fclose(fp);

    if (pid <= 0) {
        return -1;
    }

    return (pid_t)pid;
}

static int process_exists(pid_t pid)
{
    if (pid <= 0) {
        return 0;
    }

    if (kill(pid, 0) == 0) {
        return 1;
    }

    if (errno == EPERM) {
        return 1;
    }

    return 0;
}

static int pid_is_nwwebui(pid_t pid)
{
    char path[128];
    char name[256];
    FILE *fp;

    snprintf(path, sizeof(path), "/proc/%ld/comm", (long)pid);

    fp = fopen(path, "r");
    if (!fp) {
        return 0;
    }

    if (!fgets(name, sizeof(name), fp)) {
        fclose(fp);
        return 0;
    }

    fclose(fp);

    name[strcspn(name, "\r\n")] = '\0';

    return strcmp(name, "nwwebui") == 0;
}

static int stop_running_instance(const char *pid_file)
{
    pid_t pid = read_pid_file(pid_file);

    if (pid <= 0) {
        fprintf(stderr, "Could not read PID file: %s\n", pid_file);
        return EXIT_FAILURE;
    }

    if (!process_exists(pid)) {
        fprintf(stderr, "No running process found for PID %ld\n", (long)pid);
        unlink(pid_file);
        return EXIT_FAILURE;
    }

    if (!pid_is_nwwebui(pid)) {
        fprintf(stderr, "PID %ld is not a nwwebui process\n", (long)pid);
        return EXIT_FAILURE;
    }

    if (kill(pid, SIGTERM) < 0) {
        fprintf(stderr, "Failed to stop nwwebui with PID %ld: %s\n",
                (long)pid, strerror(errno));
        return EXIT_FAILURE;
    }

    fprintf(stderr, "Sent SIGTERM to %s %s with PID %ld\n",
            NWWEBUI_NAME, NWWEBUI_VERSION, (long)pid);
    return EXIT_SUCCESS;
}

/* ------------------------------------------------------------ */
/* Daemonization                                                */
/* ------------------------------------------------------------ */

static void daemonize_process(void)
{
    pid_t pid;

    pid = fork();
    if (pid < 0) {
        die("fork");
    }
    if (pid > 0) {
        exit(EXIT_SUCCESS);
    }

    if (setsid() < 0) {
        die("setsid");
    }

    pid = fork();
    if (pid < 0) {
        die("fork");
    }
    if (pid > 0) {
        exit(EXIT_SUCCESS);
    }

    if (chdir("/") < 0) {
        die("chdir");
    }

    umask(0);

    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    open("/dev/null", O_RDONLY);
    open("/dev/null", O_WRONLY);
    open("/dev/null", O_WRONLY);
}

/* ------------------------------------------------------------ */
/* smart.conf loading                                           */
/* ------------------------------------------------------------ */

static void init_defaults(nw_config_t *cfg, const char *smart_conf_path)
{
    memset(cfg, 0, sizeof(*cfg));

    snprintf(cfg->bind_ip, sizeof(cfg->bind_ip), "%s", DEFAULT_BIND_IP);

    cfg->ssl_enable = DEFAULT_SSL_ENABLE;
    cfg->http_port = DEFAULT_HTTP_PORT;
    cfg->https_port = DEFAULT_HTTPS_PORT;
    cfg->daemonize = DEFAULT_DAEMONIZE;

    snprintf(cfg->cert_file, sizeof(cfg->cert_file), "%s", DEFAULT_CERT_FILE);
    snprintf(cfg->key_file, sizeof(cfg->key_file), "%s", DEFAULT_KEY_FILE);
    snprintf(cfg->pid_file, sizeof(cfg->pid_file), "%s", DEFAULT_PID_FILE);
    snprintf(cfg->log_file, sizeof(cfg->log_file), "%s", LOG_PATH_DEFAULT);

    snprintf(cfg->smart_perl_path, sizeof(cfg->smart_perl_path), "%s", DEFAULT_SMART_PERL);
    snprintf(cfg->smart_conf, sizeof(cfg->smart_conf), "%s", smart_conf_path);

    snprintf(g_log_path, sizeof(g_log_path), "%s", LOG_PATH_DEFAULT);
}

static void load_smart_conf(nw_config_t *cfg)
{
    FILE *fp = fopen(cfg->smart_conf, "r");
    char line[2048];

    if (!fp) {
        log_msg(LOG_LEVEL_ERROR, "Could not open smart.conf: %s", cfg->smart_conf);
        die("fopen smart.conf");
    }

    while (fgets(line, sizeof(line), fp)) {
        char key[256];
        char val[1024];

        if (!parse_perl_assignment(line, key, sizeof(key), val, sizeof(val))) {
            continue;
        }

        if (strcmp(key, "nw_bind_ip") == 0) {
            snprintf(cfg->bind_ip, sizeof(cfg->bind_ip), "%s", val);
        } else if (strcmp(key, "nw_log_level") == 0) {
            g_log_level = atoi(val);
        } else if (strcmp(key, "nw_log_file") == 0) {
            snprintf(cfg->log_file, sizeof(cfg->log_file), "%s", val);
        } else if (strcmp(key, "nw_ssl_enable") == 0) {
            cfg->ssl_enable = atoi(val);
        } else if (strcmp(key, "nw_http_port") == 0) {
            cfg->http_port = atoi(val);
        } else if (strcmp(key, "nw_https_port") == 0) {
            cfg->https_port = atoi(val);
        } else if (strcmp(key, "nw_daemonize") == 0) {
            cfg->daemonize = atoi(val);
        } else if (strcmp(key, "nw_pid_file") == 0) {
            snprintf(cfg->pid_file, sizeof(cfg->pid_file), "%s", val);
        } else if (strcmp(key, "nw_cert_file") == 0) {
            snprintf(cfg->cert_file, sizeof(cfg->cert_file), "%s", val);
        } else if (strcmp(key, "nw_key_file") == 0) {
            snprintf(cfg->key_file, sizeof(cfg->key_file), "%s", val);
        } else if (strcmp(key, "smart_perl_path") == 0) {
            snprintf(cfg->smart_perl_path, sizeof(cfg->smart_perl_path), "%s", val);
        }
    }

    fclose(fp);

    if (cfg->http_port < 0) {
        cfg->http_port = DEFAULT_HTTP_PORT;
    }
    if (cfg->https_port < 0) {
        cfg->https_port = DEFAULT_HTTPS_PORT;
    }

    if (g_log_level < LOG_LEVEL_ERROR) {
        g_log_level = LOG_LEVEL_DEFAULT;
    }
    if (g_log_level > LOG_LEVEL_DEBUG) {
        g_log_level = LOG_LEVEL_DEBUG;
    }

    snprintf(g_log_path, sizeof(g_log_path), "%s", cfg->log_file);
}

/* ------------------------------------------------------------ */
/* Listener / socket helpers                                    */
/* ------------------------------------------------------------ */

static int create_listener(const char *bind_ip, int port)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    int yes = 1;
    struct sockaddr_in addr;

    if (fd < 0) {
        die("socket");
    }

    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
        close(fd);
        die("setsockopt(SO_REUSEADDR)");
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons((uint16_t)port);

    if (strcmp(bind_ip, "0.0.0.0") == 0) {
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
    } else {
        if (inet_pton(AF_INET, bind_ip, &addr.sin_addr) != 1) {
            close(fd);
            log_msg(LOG_LEVEL_ERROR, "Invalid bind IP: %s", bind_ip);
            exit(EXIT_FAILURE);
        }
    }

    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        close(fd);
        die("bind");
    }

    if (listen(fd, NW_BACKLOG) < 0) {
        close(fd);
        die("listen");
    }

    return fd;
}

static ssize_t write_all_fd(int fd, const unsigned char *buf, size_t len)
{
    size_t off = 0;

    while (off < len) {
        ssize_t n = write(fd, buf + off, len - off);
        if (n < 0) {
            if (errno == EINTR) {
                continue;
            }
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                continue;
            }
            return -1;
        }
        if (n == 0) {
            break;
        }
        off += (size_t)n;
    }

    return (ssize_t)off;
}

static ssize_t send_all_ssl(SSL *ssl, const unsigned char *buf, size_t len)
{
    size_t off = 0;

    while (off < len) {
        int n = SSL_write(ssl, buf + off, (int)(len - off));
        if (n <= 0) {
            int err = SSL_get_error(ssl, n);
            if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
                continue;
            }
            return -1;
        }
        off += (size_t)n;
    }

    return (ssize_t)off;
}

/* ------------------------------------------------------------ */
/* Perl launcher                                                */
/* ------------------------------------------------------------ */

static pid_t spawn_smart_perl(const nw_config_t *cfg, int *child_stdin_fd, int *child_stdout_fd)
{
    int inpipe[2] = { -1, -1 };
    int outpipe[2] = { -1, -1 };
    pid_t pid;

    if (pipe(inpipe) < 0) {
        die("pipe stdin");
    }

    if (pipe(outpipe) < 0) {
        close(inpipe[0]);
        close(inpipe[1]);
        die("pipe stdout");
    }

    pid = fork();
    if (pid < 0) {
        close(inpipe[0]);
        close(inpipe[1]);
        close(outpipe[0]);
        close(outpipe[1]);
        die("fork");
    }

    if (pid == 0) {
        if (dup2(inpipe[0], STDIN_FILENO) < 0) {
            perror("dup2 stdin");
            _exit(127);
        }
        if (dup2(outpipe[1], STDOUT_FILENO) < 0) {
            perror("dup2 stdout");
            _exit(127);
        }
        if (dup2(outpipe[1], STDERR_FILENO) < 0) {
            perror("dup2 stderr");
            _exit(127);
        }

        close(inpipe[0]);
        close(inpipe[1]);
        close(outpipe[0]);
        close(outpipe[1]);

        execl(cfg->smart_perl_path, cfg->smart_perl_path, (char *)NULL);

        perror("execl smart_perl_path");
        _exit(127);
    }

    close(inpipe[0]);
    close(outpipe[1]);

    *child_stdin_fd = inpipe[1];
    *child_stdout_fd = outpipe[0];

    return pid;
}

/* ------------------------------------------------------------ */
/* Shared child cleanup                                         */
/* ------------------------------------------------------------ */

static void cleanup_child_process(int *child_stdin_fd, int *child_stdout_fd, pid_t *child_pid)
{
    if (*child_stdin_fd >= 0) {
        close(*child_stdin_fd);
        *child_stdin_fd = -1;
    }

    if (*child_stdout_fd >= 0) {
        close(*child_stdout_fd);
        *child_stdout_fd = -1;
    }

    if (*child_pid > 0) {
        int status = 0;
        pid_t w = waitpid(*child_pid, &status, WNOHANG);

        if (w == 0) {
            kill(*child_pid, SIGTERM);
            waitpid(*child_pid, &status, 0);
        } else if (w < 0 && errno != ECHILD) {
            log_msg(LOG_LEVEL_ERROR, "waitpid failed for smart child: %s", strerror(errno));
        }

        *child_pid = -1;
    }
}

/* ------------------------------------------------------------ */
/* Plain HTTP handler                                           */
/* ------------------------------------------------------------ */

static void handle_connection_plain(int client_fd, const nw_config_t *cfg)
{
    int child_stdin_fd = -1;
    int child_stdout_fd = -1;
    pid_t child_pid = -1;
    unsigned char buf[NW_BUF_SZ];

    child_pid = spawn_smart_perl(cfg, &child_stdin_fd, &child_stdout_fd);

    if (set_nonblocking(client_fd) < 0) {
        log_msg(LOG_LEVEL_ERROR, "Could not set client socket non-blocking");
        goto cleanup;
    }

    if (set_nonblocking(child_stdout_fd) < 0) {
        log_msg(LOG_LEVEL_ERROR, "Could not set child stdout non-blocking");
        goto cleanup;
    }

    for (;;) {
        struct pollfd pfds[2];
        int pr;

        pfds[0].fd = client_fd;
        pfds[0].events = POLLIN | POLLHUP | POLLERR;
        pfds[0].revents = 0;

        pfds[1].fd = child_stdout_fd;
        pfds[1].events = POLLIN | POLLHUP | POLLERR;
        pfds[1].revents = 0;

        pr = poll(pfds, 2, -1);
        if (pr < 0) {
            if (errno == EINTR) {
                continue;
            }
            log_msg(LOG_LEVEL_ERROR, "poll failed in plain handler: %s", strerror(errno));
            break;
        }

        if (pfds[0].revents & (POLLIN | POLLHUP | POLLERR)) {
            ssize_t n = read(client_fd, buf, sizeof(buf));
            if (n > 0) {
                if (write_all_fd(child_stdin_fd, buf, (size_t)n) < 0) {
                    log_msg(LOG_LEVEL_ERROR, "Write to Perl stdin failed");
                    break;
                }
            } else if (n == 0) {
                break;
            } else if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
                log_msg(LOG_LEVEL_ERROR, "Read from HTTP client failed: %s", strerror(errno));
                break;
            }
        }

        if (pfds[1].revents & (POLLIN | POLLHUP | POLLERR)) {
            ssize_t rn = read(child_stdout_fd, buf, sizeof(buf));
            if (rn > 0) {
                if (write_all_fd(client_fd, buf, (size_t)rn) < 0) {
                    log_msg(LOG_LEVEL_ERROR, "Write to HTTP client failed");
                    break;
                }
            } else if (rn == 0) {
                break;
            } else if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
                log_msg(LOG_LEVEL_ERROR, "Read from Perl stdout failed: %s", strerror(errno));
                break;
            }
        }

        if (child_pid > 0) {
            int status = 0;
            pid_t w = waitpid(child_pid, &status, WNOHANG);
            if (w == child_pid) {
                break;
            }
        }
    }

cleanup:
    cleanup_child_process(&child_stdin_fd, &child_stdout_fd, &child_pid);
    close(client_fd);
}

/* ------------------------------------------------------------ */
/* TLS handler                                                  */
/* ------------------------------------------------------------ */

static void handle_connection_tls(SSL_CTX *ctx, int client_fd, const nw_config_t *cfg)
{
    SSL *ssl = NULL;
    int child_stdin_fd = -1;
    int child_stdout_fd = -1;
    pid_t child_pid = -1;
    unsigned char buf[NW_BUF_SZ];

    if (set_nonblocking(client_fd) < 0) {
        log_msg(LOG_LEVEL_ERROR, "Could not set client socket non-blocking");
        goto cleanup;
    }

    ssl = SSL_new(ctx);
    if (!ssl) {
        log_msg(LOG_LEVEL_ERROR, "SSL_new failed");
        goto cleanup;
    }

    SSL_set_fd(ssl, client_fd);

    if (SSL_accept(ssl) <= 0) {
        log_msg(LOG_LEVEL_ERROR, "SSL_accept failed");
        ERR_print_errors_fp(g_log_fp ? g_log_fp : stderr);
        goto cleanup;
    }

    log_msg(LOG_LEVEL_INFO, "Accepted new TLS connection");

    child_pid = spawn_smart_perl(cfg, &child_stdin_fd, &child_stdout_fd);

    if (set_nonblocking(child_stdout_fd) < 0) {
        log_msg(LOG_LEVEL_ERROR, "Could not set child stdout non-blocking");
        goto cleanup;
    }

    for (;;) {
        struct pollfd pfds[2];
        int pr;

        pfds[0].fd = client_fd;
        pfds[0].events = POLLIN | POLLHUP | POLLERR;
        pfds[0].revents = 0;

        pfds[1].fd = child_stdout_fd;
        pfds[1].events = POLLIN | POLLHUP | POLLERR;
        pfds[1].revents = 0;

        pr = poll(pfds, 2, -1);
        if (pr < 0) {
            if (errno == EINTR) {
                continue;
            }
            log_msg(LOG_LEVEL_ERROR, "poll failed in TLS handler: %s", strerror(errno));
            break;
        }

        if ((pfds[0].revents & (POLLIN | POLLHUP | POLLERR)) || SSL_pending(ssl) > 0) {
            int n = SSL_read(ssl, buf, sizeof(buf));
            if (n > 0) {
                if (write_all_fd(child_stdin_fd, buf, (size_t)n) < 0) {
                    log_msg(LOG_LEVEL_ERROR, "Write to Perl stdin failed");
                    break;
                }
            } else {
                int err = SSL_get_error(ssl, n);
                if (err == SSL_ERROR_ZERO_RETURN) {
                    break;
                }
                if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
                    log_msg(LOG_LEVEL_ERROR, "SSL_read failed");
                    break;
                }
            }
        }

        if (pfds[1].revents & (POLLIN | POLLHUP | POLLERR)) {
            ssize_t rn = read(child_stdout_fd, buf, sizeof(buf));
            if (rn > 0) {
                if (send_all_ssl(ssl, buf, (size_t)rn) < 0) {
                    log_msg(LOG_LEVEL_ERROR, "SSL_write failed");
                    break;
                }
            } else if (rn == 0) {
                break;
            } else if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
                log_msg(LOG_LEVEL_ERROR, "Read from Perl stdout failed: %s", strerror(errno));
                break;
            }
        }

        if (child_pid > 0) {
            int status = 0;
            pid_t w = waitpid(child_pid, &status, WNOHANG);
            if (w == child_pid) {
                break;
            }
        }
    }

cleanup:
    cleanup_child_process(&child_stdin_fd, &child_stdout_fd, &child_pid);

    if (ssl) {
        SSL_shutdown(ssl);
        SSL_free(ssl);
    }

    close(client_fd);
}

/* ------------------------------------------------------------ */
/* Signal handlers                                              */
/* ------------------------------------------------------------ */

static void reap_children(int sig)
{
    (void)sig;

    for (;;) {
        pid_t pid = waitpid(-1, NULL, WNOHANG);
        if (pid <= 0) {
            break;
        }
    }
}

static void term_handler(int sig)
{
    g_got_signal = sig;
    g_terminate = 1;
}

/* ------------------------------------------------------------ */
/* Main                                                         */
/* ------------------------------------------------------------ */

int main(int argc, char **argv)
{
    const char *smart_conf_path = DEFAULT_SMART_CONF;
    const char *pidfile_override = NULL;
    const char *logfile_override = NULL;
    int http_fd = -1;
    int https_fd = -1;
    SSL_CTX *ctx = NULL;
    int force_daemon = 0;
    int stop_mode = 0;
    int i;

    for (i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
            print_usage(argv[0]);
            return EXIT_SUCCESS;
        } else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--daemon") == 0) {
            force_daemon = 1;
        } else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--stop") == 0) {
            stop_mode = 1;
        } else if ((strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config") == 0) && i + 1 < argc) {
            smart_conf_path = argv[++i];
        } else if ((strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--pidfile") == 0) && i + 1 < argc) {
            pidfile_override = argv[++i];
        } else if ((strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--logfile") == 0) && i + 1 < argc) {
            logfile_override = argv[++i];
        } else {
            fprintf(stderr, "Unknown argument: %s\n", argv[i]);
            print_usage(argv[0]);
            return EXIT_FAILURE;
        }
    }

    {
        struct sigaction sa;
        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = reap_children;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;

        if (sigaction(SIGCHLD, &sa, NULL) < 0) {
            die("sigaction(SIGCHLD)");
        }
    }

    {
        struct sigaction sa;
        memset(&sa, 0, sizeof(sa));
        sa.sa_handler = term_handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;

        if (sigaction(SIGTERM, &sa, NULL) < 0) {
            die("sigaction(SIGTERM)");
        }
        if (sigaction(SIGINT, &sa, NULL) < 0) {
            die("sigaction(SIGINT)");
        }
        if (sigaction(SIGHUP, &sa, NULL) < 0) {
            die("sigaction(SIGHUP)");
        }
    }

    signal(SIGPIPE, SIG_IGN);

    console_msg("%s %s starting", NWWEBUI_NAME, NWWEBUI_VERSION);

    {
        nw_config_t cfg;
        init_defaults(&cfg, smart_conf_path);
        load_smart_conf(&cfg);

        if (force_daemon) {
            cfg.daemonize = 1;
        }

        if (pidfile_override) {
            snprintf(cfg.pid_file, sizeof(cfg.pid_file), "%s", pidfile_override);
        }

        if (logfile_override) {
            snprintf(cfg.log_file, sizeof(cfg.log_file), "%s", logfile_override);
        }

        log_reopen(cfg.log_file);

        if (stop_mode) {
            return stop_running_instance(cfg.pid_file);
        }

        if (!file_exists_and_executable(cfg.smart_perl_path)) {
            log_msg(LOG_LEVEL_ERROR,
                    "SMArT Perl program is missing or not executable: %s",
                    cfg.smart_perl_path);
            return EXIT_FAILURE;
        }

        log_msg(LOG_LEVEL_INFO, "%s version %s starting", NWWEBUI_NAME, NWWEBUI_VERSION);
        log_msg(LOG_LEVEL_INFO, "Using SMArT Perl path: %s", cfg.smart_perl_path);
        log_msg(LOG_LEVEL_INFO,
                "%s version %s config loaded: bind=%s log_level=%d ssl_enable=%d http_port=%d https_port=%d daemonize=%d cert=%s key=%s pid_file=%s log_file=%s smart.conf=%s",
                NWWEBUI_NAME,
                NWWEBUI_VERSION,
                cfg.bind_ip,
                g_log_level,
                cfg.ssl_enable,
                cfg.http_port,
                cfg.https_port,
                cfg.daemonize,
                cfg.cert_file,
                cfg.key_file,
                cfg.pid_file,
                cfg.log_file,
                cfg.smart_conf);

        if (cfg.daemonize) {
            console_msg("%s %s switching to daemon mode", NWWEBUI_NAME, NWWEBUI_VERSION);
            daemonize_process();
            log_reopen(cfg.log_file);
        }

        write_pid_file(cfg.pid_file);
        atexit(remove_pid_file);
        log_msg(LOG_LEVEL_INFO, "Wrote PID file: %s", cfg.pid_file);

        if (cfg.ssl_enable) {
            SSL_library_init();
            SSL_load_error_strings();
            OpenSSL_add_ssl_algorithms();

            ctx = SSL_CTX_new(TLS_server_method());
            if (!ctx) {
                ssl_die("SSL_CTX_new failed");
            }

            SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);

            if (SSL_CTX_use_certificate_file(ctx, cfg.cert_file, SSL_FILETYPE_PEM) <= 0) {
                ssl_die("Could not load certificate file");
            }

            if (SSL_CTX_use_PrivateKey_file(ctx, cfg.key_file, SSL_FILETYPE_PEM) <= 0) {
                ssl_die("Could not load private key file");
            }

            if (!SSL_CTX_check_private_key(ctx)) {
                ssl_die("Private key does not match certificate");
            }
        }

        if (cfg.http_port > 0) {
            http_fd = create_listener(cfg.bind_ip, cfg.http_port);
            log_msg(LOG_LEVEL_INFO,
                    "HTTP listener active on %s:%d",
                    cfg.bind_ip,
                    cfg.http_port);
        }

        if (cfg.ssl_enable && cfg.https_port > 0) {
            https_fd = create_listener(cfg.bind_ip, cfg.https_port);
            log_msg(LOG_LEVEL_INFO,
                    "HTTPS listener active on %s:%d",
                    cfg.bind_ip,
                    cfg.https_port);
        }

        if (http_fd < 0 && https_fd < 0) {
            log_msg(LOG_LEVEL_ERROR, "No listener is active");
            if (ctx) {
                SSL_CTX_free(ctx);
            }
            return EXIT_FAILURE;
        }

        console_msg("%s %s started", NWWEBUI_NAME, NWWEBUI_VERSION);

        for (;;) {
            struct pollfd pfds[2];
            int nfds = 0;
            int pr;

            if (g_terminate) {
                console_msg("%s received signal %d, shutting down", NWWEBUI_NAME, g_got_signal);
                log_msg(LOG_LEVEL_INFO, "Termination requested by signal %d", g_got_signal);
                break;
            }

            if (http_fd >= 0) {
                pfds[nfds].fd = http_fd;
                pfds[nfds].events = POLLIN;
                pfds[nfds].revents = 0;
                nfds++;
            }

            if (https_fd >= 0) {
                pfds[nfds].fd = https_fd;
                pfds[nfds].events = POLLIN;
                pfds[nfds].revents = 0;
                nfds++;
            }

            pr = poll(pfds, nfds, 1000);
            if (pr < 0) {
                if (errno == EINTR) {
                    continue;
                }
                log_msg(LOG_LEVEL_ERROR, "poll failed: %s", strerror(errno));
                continue;
            }

            if (pr == 0) {
                continue;
            }

            for (i = 0; i < nfds; i++) {
                struct sockaddr_in peer;
                socklen_t peerlen = sizeof(peer);
                int client_fd;
                pid_t pid;

                if (!(pfds[i].revents & POLLIN)) {
                    continue;
                }

                client_fd = accept(pfds[i].fd, (struct sockaddr *)&peer, &peerlen);
                if (client_fd < 0) {
                    if (errno == EINTR) {
                        continue;
                    }
                    log_msg(LOG_LEVEL_ERROR, "accept failed: %s", strerror(errno));
                    continue;
                }

                pid = fork();
                if (pid < 0) {
                    log_msg(LOG_LEVEL_ERROR, "fork failed: %s", strerror(errno));
                    close(client_fd);
                    continue;
                }

                if (pid == 0) {
                    if (http_fd >= 0) {
                        close(http_fd);
                    }
                    if (https_fd >= 0) {
                        close(https_fd);
                    }

                    if (pfds[i].fd == https_fd) {
                        handle_connection_tls(ctx, client_fd, &cfg);
                    } else {
                        handle_connection_plain(client_fd, &cfg);
                    }

                    if (ctx) {
                        SSL_CTX_free(ctx);
                    }
                    _exit(0);
                }

                close(client_fd);
            }
        }
    }

    if (http_fd >= 0) {
        close(http_fd);
    }
    if (https_fd >= 0) {
        close(https_fd);
    }
    if (ctx) {
        SSL_CTX_free(ctx);
    }

    console_msg("%s stopped", NWWEBUI_NAME);
    return 0;
}
