6 ex__network__ssl_8c
gullradriel edited this page 2026-10-09 00:55:18 +02:00

examples/ex_network_ssl.c

Structures

Name
struct NETWORK_SSL_THREAD_PARAMS
structure of a NETWORK_SSL_THREAD_PARAMS

Functions

Name
void usage(void )
Network for managing connections.
void * ssl_network_thread(void * params)
void process_args(int argc_nb, char ** argv_ptr, char ** addr_ptr, char ** port_ptr, char ** key_ptr, char ** cert_ptr, char ** ca_file_ptr, int * ssl_verify_ptr, LIST * routes_ptr, int * ip_version_ptr, int * max_http_request_size_ptr, char ** root_dir_ptr)
int main(int argc, char * argv[])
void handle_request(NETWORK * netw_ptr, LIST * routes_ptr)
void action_on_sig(int recvd_signal)

Attributes

Name
int ssl_verify
NETWORK * server
LIST * routes
char * root_dir
char * port
NETWORK * netw
Network for server mode, accepting incomming.
int max_http_request_size
int max_connections
volatile sig_atomic_t killed_by_signal
int ip_version
volatile sig_atomic_t done
char * cert
char * ca_file
char * addr
int accept_timeout

Functions Documentation

function usage

void usage(
    void 
)

Network for managing connections.

function ssl_network_thread

void * ssl_network_thread(
    void * params
)

function process_args

void process_args(
    int argc_nb,
    char ** argv_ptr,
    char ** addr_ptr,
    char ** port_ptr,
    char ** key_ptr,
    char ** cert_ptr,
    char ** ca_file_ptr,
    int * ssl_verify_ptr,
    LIST * routes_ptr,
    int * ip_version_ptr,
    int * max_http_request_size_ptr,
    char ** root_dir_ptr
)

function main

int main(
    int argc,
    char * argv[]
)

function handle_request

void handle_request(
    NETWORK * netw_ptr,
    LIST * routes_ptr
)

function action_on_sig

void action_on_sig(
    int recvd_signal
)

Attributes Documentation

variable ssl_verify

int ssl_verify = 0;

variable server

NETWORK * server = NULL;

variable routes

LIST * routes = NULL;

variable root_dir

char * root_dir = NULL;

variable port

char * port = NULL;

variable netw

NETWORK * netw = NULL;

Network for server mode, accepting incomming.

variable max_http_request_size

int max_http_request_size = 16384;

variable max_connections

int max_connections = 0;

variable killed_by_signal

volatile sig_atomic_t killed_by_signal = 0;

variable ip_version

int ip_version = 0;

variable done

volatile sig_atomic_t done = 0;

variable cert

char * cert = NULL;

variable ca_file

char * ca_file = NULL;

variable addr

char * addr = NULL;

variable accept_timeout

int accept_timeout = 30;

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include "nilorea/n_list.h"
#include "nilorea/n_str.h"
#include "nilorea/n_log.h"
#include "nilorea/n_network.h"
#include "nilorea/n_thread_pool.h"
#include "nilorea/n_signals.h"

#include <sys/stat.h>

char* port = NULL;
char* addr = NULL;
char* key = NULL;
char* cert = NULL;
char* ca_file = NULL;
char* root_dir = NULL;
LIST* routes = NULL;
int ip_version = NETWORK_IPALL;
int max_http_request_size = 16384;
int ssl_verify = 0;
int max_connections = 0; /* 0 = unlimited */
/* Seconds to wait for a connection before giving up, 0 waits forever.
 * Without it a server whose client never showed up sat in accept()
 * until the test harness killed it, reported as a bare exit 124. */
int accept_timeout = 30;
volatile sig_atomic_t done = 0;
/* set by the SIGTERM handler so the exit status still reports the kill */
volatile sig_atomic_t killed_by_signal = 0;

NETWORK *server = NULL, 
    *netw = NULL;       

void usage(void) {
    fprintf(stderr,
            "     -p 'port'            : set the https server port\n"
            "     -k 'key file'        : SSL key file path\n"
            "     -c 'cert file'       : SSL certificate file path\n"
            "     -A 'CA file'         : optional, CA certificate file for chain verification\n"
            "     -e                   : optional, enable SSL peer certificate verification\n"
            "     -a 'address name/ip' : optional, specify where to bind interface\n"
            "     -i 'ipmode'          : optional, force 'ipv4' or 'ipv6', default supports both\n"
            "     -s 'size'            : optional, maximum http request size (default: %d)\n"
            "     -n 'count'           : optional, exit after count connections (0 = unlimited, default)\n"
            "     -w 'seconds'         : optional, give up after seconds without a connection (0 = wait forever, default: 30)\n"
            "     -d 'html root'       : optional, specify a different http root dir (default: ./DATAS/)\n"
            "     -v                   : version\n"
            "     -h                   : help\n"
            "     -V 'log level'       : optional, set the log level (default: LOG_ERR)\n",
            max_http_request_size);
}

void process_args(int argc_nb, char** argv_ptr, char** addr_ptr, char** port_ptr, char** key_ptr, char** cert_ptr, char** ca_file_ptr, int* ssl_verify_ptr, LIST* routes_ptr, int* ip_version_ptr, int* max_http_request_size_ptr, char** root_dir_ptr) {
    int getoptret = 0,
        log_level = LOG_ERR; /* default log level */

    if (argc_nb == 1) {
        fprintf(stderr, "No arguments given, help:\n");
        usage();
        exit(1);
    }
    while ((getoptret = getopt(argc_nb, argv_ptr, "hven:s:V:p:i:a:r:k:c:s:d:A:w:")) != EOF) {
        switch (getoptret) {
            case 'n':
                max_connections = atoi(optarg);
                break;
            case 'w':
                accept_timeout = atoi(optarg);
                break;
            case 'i':
                if (!strcmp("v4", optarg)) {
                    (*ip_version_ptr) = NETWORK_IPV4;
                    n_log(LOG_NOTICE, "IPV4 selected");
                } else if (!strcmp("v6", optarg)) {
                    (*ip_version_ptr) = NETWORK_IPV6;
                    n_log(LOG_NOTICE, "IPV6 selected");
                } else {
                    n_log(LOG_NOTICE, "IPV4/6 selected");
                }
                break;
            case 'v':
                fprintf(stderr, "Date de compilation : %s a %s.\n", __DATE__, __TIME__);
                exit(1);
            case 'V':
                if (!strncmp("LOG_NULL", optarg, 8)) {
                    log_level = LOG_NULL;
                } else {
                    if (!strncmp("LOG_NOTICE", optarg, 10)) {
                        log_level = LOG_NOTICE;
                    } else {
                        if (!strncmp("LOG_INFO", optarg, 8)) {
                            log_level = LOG_INFO;
                        } else {
                            if (!strncmp("LOG_ERR", optarg, 7)) {
                                log_level = LOG_ERR;
                            } else {
                                if (!strncmp("LOG_DEBUG", optarg, 9)) {
                                    log_level = LOG_DEBUG;
                                } else {
                                    fprintf(stderr, "%s n'est pas un niveau de log valide.\n", optarg);
                                    exit(-1);
                                }
                            }
                        }
                    }
                }
                break;
            case 'e':
                (*ssl_verify_ptr) = 1;
                break;
            case 'p':
                (*port_ptr) = strdup(optarg);
                break;
            case 'r':
                list_push(routes_ptr, strdup(optarg), &free);
                break;
            case 'a':
                (*addr_ptr) = strdup(optarg);
                break;
            case 'k':
                (*key_ptr) = strdup(optarg);
                break;
            case 'c':
                (*cert_ptr) = strdup(optarg);
                break;
            case 'A':
                (*ca_file_ptr) = strdup(optarg);
                break;
            case 's':
                (*max_http_request_size_ptr) = atoi(optarg);
                break;
            case 'd':
                (*root_dir_ptr) = strdup(optarg);
                break;
            default:
            case '?': {
                if (optopt == 'd') {
                    fprintf(stderr, "\n      Missing html root directory\n");
                }
                if (optopt == 's') {
                    fprintf(stderr, "\n      Missing max http size string\n");
                }
                if (optopt == 'k') {
                    fprintf(stderr, "\n      Missing key file string\n");
                }
                if (optopt == 'c') {
                    fprintf(stderr, "\n      Missing certificate file string\n");
                }
                if (optopt == 'A') {
                    fprintf(stderr, "\n      Missing CA file string\n");
                }
                if (optopt == 'r') {
                    fprintf(stderr, "\n      Missing route string\n");
                }
                if (optopt == 'a') {
                    fprintf(stderr, "\n      Missing binding host/addr string\n");
                }
                if (optopt == 'i') {
                    fprintf(stderr, "\n      Missing ip version (v4 or v6) string \n");
                } else if (optopt == 'V') {
                    fprintf(stderr, "\n      Missing log level string\n");
                } else if (optopt == 'p') {
                    fprintf(stderr, "\n      Missing port\n");
                } else if (optopt != 's') {
                    fprintf(stderr, "\n      Unknow missing option %c\n", optopt);
                }
                usage();
                exit(1);
            }
            case 'h': {
                usage();
                exit(1);
            }
        }
    }
    set_log_level(log_level);
} /* void process_args( ... ) */

/* Exit handling  */
void action_on_sig(int recvd_signal) {
    (void)recvd_signal;
#ifndef __windows__
    static int nb_sigterm = 0;
    switch (recvd_signal) {
        /* We should not use these signals as they make the debugging going funky */
        case (SIGABRT):
            n_log(LOG_ERR, "Caught SIGABRT !");
            break;
        case (SIGINT):
            n_log(LOG_ERR, "Caught SIGINT !");
            break;
        case (SIGBUS):
            n_log(LOG_ERR, "Caught SIGBUS !");
            break;
        case (SIGFPE):
            n_log(LOG_ERR, "Caught SIGFPE !");
            break;
        case (SIGSEGV):
            n_log(LOG_ERR, "Caught SIGSEGV !");
            break;
        case (SIGSYS):
            n_log(LOG_ERR, "Caught SIGSYS !");
            break;
        case (SIGTERM):
            nb_sigterm++;
            if (nb_sigterm >= 2) {
                /* the accept loop did not pick the flag up, leave without
                 * unwinding: exit() would run OpenSSL's atexit cleanup and
                 * free() is not async-signal-safe */
                _exit(-1);
            }
            /* no n_log() here either, just raise the flag and let the accept
             * loop report it and run the normal teardown */
            killed_by_signal = 1;
            done = TRUE;
            break;
        case (SIGUSR1):
            done = TRUE;
            n_log(LOG_ERR, "Caught SIGUSR1 !");
            break;
        case (SIGUSR2):
            done = TRUE;
            n_log(LOG_ERR, "Caught SIGUSR2 !");
            break;
        case (SIGHUP):
            n_log(LOG_NOTICE, "Caught SIGHUP !");
            break;
        default:
            n_log(LOG_ERR, "Caught unknow signal %d", recvd_signal);
            break;
    }
#endif
} /* action_on_sig() */

// Function to handle different URLs and return appropriate responses
void handle_request(NETWORK* netw_ptr, LIST* routes_ptr) {
    __n_assert(netw_ptr, return);
    __n_assert(routes_ptr, return);

    bool found = 0;
    char** split_results = NULL;
    char* http_url = NULL;
    N_STR* dynamic_request_answer = NULL;

    /* the caller (ssl_network_thread) owns netw_ptr and closes it, closing it
     * here too would leave it with a dangling pointer to close a second time */
    __n_assert(netw_ptr->ssl, n_log(LOG_ERR, "no SSL session on socket %d", netw_ptr->link.sock); return);

    // Read request
    char* http_buffer = NULL;
    Alloca(http_buffer, (size_t)(max_http_request_size + 1));
    __n_assert(http_buffer, return);

    // SSL_read reads up to max_http_request_size bytes (variable-length HTTP request).
    // recv_ssl_data is intentionally NOT used here: it loops until the entire buffer is
    // full, which would block indefinitely since HTTP requests are smaller than the buffer.
    int ssl_read_ret = SSL_read(netw_ptr->ssl, http_buffer, max_http_request_size);
    if (ssl_read_ret <= 0) {
        int ssl_error = SSL_get_error(netw_ptr->ssl, ssl_read_ret);
        if (ssl_error == SSL_ERROR_ZERO_RETURN) {
            n_log(LOG_INFO, "SSL_read: peer closed the connection cleanly");
        } else {
            n_log(LOG_ERR, "SSL_read failed with SSL error %d", ssl_error);
        }
        return;
    }
    // n_log( LOG_DEBUG , "http_request: %s" , http_buffer );

    // Extract URL from the request
    char url[4096] = "";
    netw_get_url_from_http_request(http_buffer, url, sizeof(url));
    n_log(LOG_DEBUG, "url: %s", url);

    // Handle the request based on the URL
    N_STR* origin = new_nstr(32);
    nstrprintf(origin, "%s:" SOCKET_SIZE_FORMAT, _str(netw_ptr->link.ip), netw_ptr->link.sock);

    NETWORK_HTTP_INFO http_request = netw_extract_http_info(http_buffer);
    N_STR* http_body = NULL;

    split_results = split(url, "?", 0);
    if (!split_results || !split_results[0]) {
        http_body = char_to_nstr("<html><body><h1>Bad Request</h1></body></html>");
        if (netw_build_http_response(&dynamic_request_answer, 400, "ex_network_ssl server", netw_guess_http_content_type(url), "", http_body) == FALSE) {
            n_log(LOG_ERR, "couldn't build a Bad Request answer for %s", url);
        }
        n_log(LOG_ERR, "%s: %s %s 400", _nstr(origin), http_request.type, url);
    } else {
        http_url = split_results[0];
        n_log(LOG_INFO, "%s: %s %s request...", _nstr(origin), http_request.type, url);
        if (strcmp("OPTIONS", http_request.type) == 0) {
            if (netw_build_http_response(&dynamic_request_answer, 200, "ex_network_ssl server", netw_guess_http_content_type(url), "Allow: OPTIONS, GET, POST\r\n", NULL) == FALSE) {
                n_log(LOG_ERR, "couldn't build an OPTION answer for %s", url);
            }
            n_log(LOG_INFO, "%s: %s %s 200", _nstr(origin), http_request.type, url);
        } else if (strcmp("GET", http_request.type) == 0) {
            char system_url[4096] = "";
            // example assume a root dir at DATAS
            if (!root_dir) {
                snprintf(system_url, sizeof(system_url), "./DATAS%s", http_url);
            } else {
                snprintf(system_url, sizeof(system_url), "%s%s", root_dir, http_url);
            }

            n_log(LOG_DEBUG, "%s: searching for file %s...", _nstr(origin), system_url);

            struct stat st;
            if (stat(system_url, &st) == 0 && S_ISREG(st.st_mode)) {
                n_log(LOG_DEBUG, "%s: file %s found !", _nstr(origin), system_url);
                http_body = file_to_nstr(system_url);
                if (!http_body) {
                    http_body = char_to_nstr("<html><body><h1>Internal Server Error</h1></body></html>");
                    if (netw_build_http_response(&dynamic_request_answer, 500, "ex_network_ssl server", netw_guess_http_content_type(url), "", http_body) == FALSE) {
                        n_log(LOG_ERR, "couldn't build an Internal Server Error answer for %s", url);
                    }
                    n_log(LOG_ERR, "%s: %s %s 500", _nstr(origin), http_request.type, url);
                } else {
                    if (netw_build_http_response(&dynamic_request_answer, 200, "ex_network_ssl server", netw_guess_http_content_type(url), "", http_body) == FALSE) {
                        n_log(LOG_ERR, "couldn't build an http answer for %s", url);
                    }
                    n_log(LOG_INFO, "%s: %s %s 200", _nstr(origin), http_request.type, url);
                }
            } else if (stat(system_url, &st) == 0 && S_ISDIR(st.st_mode)) {
                /* Directory: check for index.html and redirect */
                char index_path[4096 + 16] = "";
                size_t url_len = strlen(http_url);
                const char* slash = (url_len > 0 && http_url[url_len - 1] == '/') ? "" : "/";
                snprintf(index_path, sizeof(index_path), "%s%sindex.html", system_url, slash);
                struct stat idx_st;
                if (stat(index_path, &idx_st) == 0 && S_ISREG(idx_st.st_mode)) {
                    char location_header[4096] = "";
                    snprintf(location_header, sizeof(location_header), "Location: %s%sindex.html\r\n", http_url, slash);
                    if (netw_build_http_response(&dynamic_request_answer, 301, "ex_network_ssl server", "text/html", location_header, NULL) == FALSE) {
                        n_log(LOG_ERR, "couldn't build a redirect answer for %s", url);
                    }
                    n_log(LOG_INFO, "%s: %s %s 301 -> %s%sindex.html", _nstr(origin), http_request.type, url, http_url, slash);
                } else {
                    http_body = char_to_nstr("<html><body><h1>404 Not Found</h1></body></html>");
                    if (netw_build_http_response(&dynamic_request_answer, 404, "ex_network_ssl server", netw_guess_http_content_type(url), "", http_body) == FALSE) {
                        n_log(LOG_ERR, "couldn't build a NOT FOUND answer for %s", url);
                    }
                    n_log(LOG_ERR, "%s: %s %s 404", _nstr(origin), http_request.type, url);
                }
            } else {
                http_body = char_to_nstr("<html><body><h1>404 Not Found</h1></body></html>");
                if (netw_build_http_response(&dynamic_request_answer, 404, "ex_network_ssl server", netw_guess_http_content_type(url), "", http_body) == FALSE) {
                    n_log(LOG_ERR, "couldn't build a NOT FOUND answer for %s", url);
                }
                n_log(LOG_ERR, "%s: %s %s 404", _nstr(origin), http_request.type, url);
            }
        } else if (strcmp("POST", http_request.type) == 0) {
            // Parse virtual route
            found = 0;
            list_foreach(node, routes_ptr) {
                if (strcmp(node->ptr, http_url) == 0) {
                    // Handle 200 OK from virtual route
                    HASH_TABLE* post_data = netw_parse_post_data(http_request.body);
                    if (post_data) {
                        HT_FOREACH(hnode, post_data,
                                   {
                                       n_log(LOG_DEBUG, "%s: POST DATA: %s=%s", _nstr(origin), hnode->key, (char*)hnode->data.ptr);
                                   });
                        destroy_ht(&post_data);
                    }
                    http_body = char_to_nstr("{\"status\":\"ok\"}");
                    if (netw_build_http_response(&dynamic_request_answer, 200, "ex_network_ssl server", "application/json", "", http_body) == FALSE) {
                        n_log(LOG_ERR, "couldn't build a route 200 answer for %s", url);
                    }
                    found = 1;
                    n_log(LOG_INFO, "%s: %s virtual:%s 200", _nstr(origin), http_request.type, url);
                    break;
                }
            }
            if (!found) {
                http_body = char_to_nstr("<html><body><h1>404 Not Found</h1></body></html>");
                if (netw_build_http_response(&dynamic_request_answer, 404, "ex_network_ssl server", netw_guess_http_content_type(url), "", http_body) == FALSE) {
                    n_log(LOG_ERR, "couldn't build a NOT FOUND answer for %s", url);
                }
                n_log(LOG_ERR, "%s: %s %s 404", _nstr(origin), http_request.type, url);
            }
        } else {
            http_body = char_to_nstr("<html><body><h1>Bad Request</h1></body></html>");
            if (netw_build_http_response(&dynamic_request_answer, 400, "ex_network_ssl server", netw_guess_http_content_type(url), "", http_body) == FALSE) {
                n_log(LOG_ERR, "couldn't build a Bad Request answer for %s", url);
            }
            n_log(LOG_ERR, "%s: %s %s 400", _nstr(origin), http_request.type, url);
        }
        free_split_result(&split_results);
    }
    if (dynamic_request_answer) {
        if (dynamic_request_answer->written > UINT32_MAX) {
            n_log(LOG_ERR, "response too large to send for %s: %s %s (size: %zu)", _nstr(origin), http_request.type, url, dynamic_request_answer->written);
        } else if (send_ssl_data(netw_ptr, dynamic_request_answer->data, (uint32_t)dynamic_request_answer->written) < 0) {
            n_log(LOG_ERR, "failed to send response for %s: %s %s", _nstr(origin), http_request.type, url);
        }
        free_nstr(&dynamic_request_answer);
    } else {
        n_log(LOG_ERR, "couldn't build an answer for %s: %s %s", _nstr(origin), http_request.type, url);
    }
    netw_info_destroy(http_request);
    free_nstr(&origin);
    free_nstr(&http_body);
} /* handle_request */

typedef struct NETWORK_SSL_THREAD_PARAMS {
    NETWORK* netw;
    LIST* routes;
} NETWORK_SSL_THREAD_PARAMS;

void* ssl_network_thread(void* params) {
    __n_assert(params, return NULL);
    NETWORK_SSL_THREAD_PARAMS* ssl_params = (NETWORK_SSL_THREAD_PARAMS*)params;
    handle_request(ssl_params->netw, ssl_params->routes);
    netw_close(&ssl_params->netw);
    Free(ssl_params);
    return NULL;
}

int main(int argc, char* argv[]) {
    int exit_code = 0;
    int crypto_ok = FALSE;
    THREAD_POOL* thread_pool = NULL;
    routes = new_generic_list(MAX_LIST_ITEMS);
    __n_assert(routes, n_log(LOG_ERR, "could not allocate list !"); exit(1));

    /* processing args and set log_level */
    process_args(argc, argv, &addr, &port, &key, &cert, &ca_file, &ssl_verify, routes, &ip_version, &max_http_request_size, &root_dir);

    if (!port) {
        n_log(LOG_ERR, "No port given. Exiting.");
        exit_code = 1;
        goto clean_and_exit;
    }
    if (!key) {
        n_log(LOG_ERR, "No key given. Exiting.");
        exit_code = 1;
        goto clean_and_exit;
    }
    if (!cert) {
        n_log(LOG_ERR, "No certificate given. Exiting.");
        exit_code = 1;
        goto clean_and_exit;
    }
    /*
    if (routes->nb_items == 0) {
        n_log(LOG_ERR, "No route given. Exiting.");
        exit_code = 1;
        goto clean_and_exit;
    }
    */

#ifndef __windows__
    errno = 0;
    signal(SIGPIPE, SIG_IGN);
    /* initializing signal catching */
    struct sigaction signal_catcher;

    /* quit on sig */
    signal_catcher.sa_handler = action_on_sig;
    sigemptyset(&signal_catcher.sa_mask);
    signal_catcher.sa_flags = 0;

    sigaction(SIGTERM, &signal_catcher, NULL);
    sigaction(SIGUSR1, &signal_catcher, NULL);
#endif

    long int cores = get_nb_cpu_cores();
    int nb_active_threads = (cores > 0) ? (int)cores : 1;
    int nb_waiting_threads = 10 * nb_active_threads;
    n_log(LOG_INFO, "Creating a new thread pool of %d active and %d waiting threads", nb_active_threads, nb_waiting_threads);
    thread_pool = new_thread_pool((size_t)nb_active_threads, (size_t)nb_waiting_threads);

    n_log(LOG_INFO, "Creating listening network for %s:%s %d", _str(addr), _str(port), ip_version);
    /* create listening network */
    if (netw_make_listening(&server, addr, port, SOMAXCONN, ip_version) == FALSE) {
        n_log(LOG_ERR, "Fatal error with network initialization");
        exit(-1);
    }

    /* an unreadable or malformed key/cert leaves the listener in plaintext,
     * which then fails every client handshake, so treat it as fatal here */
    if (ca_file) {
        n_log(LOG_INFO, "Using SSL with certificate chain verification (CA: %s)", ca_file);
        crypto_ok = netw_set_crypto_chain(server, key, cert, ca_file);
    } else {
        crypto_ok = netw_set_crypto(server, key, cert);
    }
    if (crypto_ok == FALSE) {
        n_log(LOG_ERR, "Could not load key '%s' and certificate '%s'. Exiting.", _str(key), _str(cert));
        exit_code = 1;
        goto clean_and_exit;
    }
    if (ssl_verify) {
        n_log(LOG_INFO, "SSL peer certificate verification enabled");
        netw_ssl_set_verify(server, 1);
    }
    int accepted_count = 0;
    /* idle watchdog: get_msec() gives the delta since the previous poll, so
     * accumulating it measures the time since the last accepted connection */
    N_TIME idle_timer;
    start_HiTimer(&idle_timer);
    time_t idle_msec = 0;
    while (!done) {
        n_log(LOG_DEBUG, "Waiting on accept...");
        /* A 500ms accept window rather than a blocking accept: it is the only
         * way the `done` flag raised by the signal handler is ever seen, and
         * it lets the watchdog below fire when no client ever shows up
         * instead of sitting here until the test harness kills us */
        int return_code = 0;
        netw = netw_accept_from_ex(server, 0, 0, 500, &return_code);
        if (!netw) {
            idle_msec += get_msec(&idle_timer);
            if (return_code == EINTR) {
                /* a signal cut select() short. Do not leave here: the loop
                 * condition is what decides whether that signal asked us to
                 * stop, and leaving directly would skip the status below */
                n_log(LOG_INFO, "accept interrupted by a signal");
            } else if (return_code != 0 && return_code != EAGAIN && return_code != EWOULDBLOCK) {
                /* 0 / EAGAIN / EWOULDBLOCK just mean the window expired with
                 * nothing waiting, which is the normal idle case */
                n_log(LOG_ERR, "error on accept, NULL netw returned !");
                u_sleep(100000);
            }
            if (accept_timeout > 0 && idle_msec > (time_t)accept_timeout * 1000) {
                n_log(LOG_ERR, "no connection for %d seconds, giving up with %d accepted", accept_timeout, accepted_count);
                exit_code = 1;
                break;
            }
            continue;
        }
        n_log(LOG_INFO, "accepted SSL connection on socket %d", netw->link.sock);
        NETWORK_SSL_THREAD_PARAMS* netw_ssl_params = NULL;
        Malloc(netw_ssl_params, NETWORK_SSL_THREAD_PARAMS, 1);
        netw_ssl_params->netw = netw;
        netw_ssl_params->routes = routes;
        if (add_threaded_process(thread_pool, &ssl_network_thread, (void*)netw_ssl_params, DIRECT_PROC) == FALSE) {
            n_log(LOG_ERR, "Error adding client management to thread pool");
        }
        accepted_count++;
        idle_msec = 0;
        get_msec(&idle_timer);
        if (max_connections > 0 && accepted_count >= max_connections) {
            n_log(LOG_NOTICE, "reached %d connections, exiting", max_connections);
            break;
        }
    }
    if (killed_by_signal) {
        n_log(LOG_ERR, "caught SIGTERM, shutting down after %d connections", accepted_count);
        exit_code = 1;
    }
clean_and_exit:
    if (thread_pool) {
        wait_for_threaded_pool(thread_pool);
        destroy_threaded_pool(&thread_pool, 1000);
    }
    netw_close(&server);
    netw_unload();
    list_destroy(&routes);
    FreeNoLog(addr);
    FreeNoLog(port);
    FreeNoLog(key);
    FreeNoLog(cert);
    FreeNoLog(ca_file);
    FreeNoLog(root_dir);
    exit(exit_code);
}