/* This file is part of Ping903
Copyright (C) 2020 Sergey Poznyakoff
Ping903 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, or (at your option)
any later version.
Ping903 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 Ping903. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "ping903.h"
#include "json.h"
#include "defs.h"
#include "basicauth.h"
char *httpd_addr;
int httpd_access_log = 0;
int httpd_log_verbose = 0;
unsigned int httpd_backlog_size = SOMAXCONN;
static int
open_node(char const *node, char const *serv, struct sockaddr **saddr)
{
struct addrinfo hints;
struct addrinfo *res;
int reuse;
int fd;
int rc;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
rc = getaddrinfo(node, serv, &hints, &res);
if (rc) {
error("%s: %s", node, gai_strerror(rc));
exit(1);
}
fd = socket(res->ai_family, res->ai_socktype, 0);
if (fd == -1) {
error("socket: %s", strerror(errno));
exit(1);
}
reuse = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
&reuse, sizeof(reuse));
if (bind(fd, res->ai_addr, res->ai_addrlen) == -1) {
error("bind: %s", strerror(errno));
exit(1);
}
if (listen(fd, 8) == -1) {
error("listen: %s", strerror(errno));
exit(1);
}
if (saddr) {
*saddr = emalloc(res->ai_addrlen);
memcpy(*saddr, res->ai_addr, res->ai_addrlen);
}
freeaddrinfo(res);
return fd;
}
static int
open_listener(char const *addr, struct sockaddr **saddr)
{
size_t len;
int fd;
if (!addr)
return open_node(DEFAULT_ADDRESS, DEFAULT_SERVICE, saddr);
len = strcspn(addr, ":");
if (len == 0)
fd = open_node(DEFAULT_ADDRESS, addr + 1, saddr);
else if (addr[len] == 0)
fd = open_node(addr, DEFAULT_SERVICE, saddr);
else {
char *node;
node = emalloc(len + 1);
memcpy(node, addr, len);
node[len] = 0;
fd = open_node(node, addr + len + 1, saddr);
free(node);
}
return fd;
}
static void
p903_httpd_logger(void *arg, const char *fmt, va_list ap)
{
vlogger(LOG_ERR, fmt, ap);
}
static void
p903_httpd_panic(void *cls, const char *file, unsigned int line,
const char *reason)
{
if (reason)
fatal("%s:%d: MHD PANIC: %s", file, line, reason);
else
fatal("%s:%d: MHD PANIC", file, line);
abort();
}
#if HAVE_LIBWRAP
extern int p903_httpd_acl(void *cls, const struct sockaddr *addr,
socklen_t addrlen);
#else
# define p903_httpd_acl NULL
#endif
static void
http_log(struct MHD_Connection *connection,
char const *method, char const *url,
int status, char const *str)
{
char *ipstr;
char const *host, *referer, *user_agent;
time_t t;
struct tm *tm;
char tbuf[30];
if (!httpd_access_log)
return;
if (!httpd_log_verbose)
str = NULL;
ipstr = get_remote_ip(connection);
host = MHD_lookup_connection_value(connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_HOST);
referer = MHD_lookup_connection_value(connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_REFERER);
user_agent = MHD_lookup_connection_value(connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_USER_AGENT);
t = time(NULL);
tm = localtime(&t);
strftime(tbuf, sizeof(tbuf), "[%d/%b/%Y:%H:%M:%S %z]", tm);
info("%s %s - - %s \"%s %s\" \"%.1024s\" %3d \"%s\" \"%s\"",
host, ipstr, tbuf, method, url, str ? str : "", status,
referer ? referer : "",
user_agent ? user_agent : "");
free(ipstr);
}
static int
http_response(struct MHD_Connection *conn,
char const *method, char const *url, int status)
{
int ret;
struct MHD_Response *resp =
MHD_create_response_from_buffer(0,
NULL,
MHD_RESPMEM_PERSISTENT);
http_log(conn, method, url, status, NULL);
ret = MHD_queue_response(conn, status, resp);
MHD_destroy_response(resp);
return ret;
}
static int httpd_json_response(struct MHD_Connection *conn,
char const *url, char const *method,
int status,
struct json_value *val);
static int
http_response_detail(struct MHD_Connection *conn,
char const *method, char const *url, int status,
char const *message, int index)
{
struct json_value *jv, *errobj = json_new_object();
int rc;
if (!errobj)
goto err;
if ((jv = json_new_string(message)) == NULL)
goto err;
if (json_object_set(errobj, "message", jv))
goto err;
if (index) {
if ((jv = json_new_number(index)) == NULL)
goto err;
if (json_object_set(errobj, "index", jv))
goto err;
}
rc = httpd_json_response(conn, url, method, status, errobj);
json_value_free(errobj);
return rc;
err:
json_value_free(errobj);
return http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
}
struct strbuf {
char *base;
size_t off;
size_t size;
int err;
};
static void
strbuf_writer(void *closure, char const *text, size_t len)
{
struct strbuf *sb = closure;
if (sb->err)
return;
while (sb->off + len >= sb->size) {
char *p = json_2nrealloc(sb->base, &sb->size, 1);
if (!p) {
error("not enough memory trying to format reply");
sb->err = 1;
return;
}
sb->base = p;
}
memcpy(sb->base + sb->off, text, len);
sb->off += len;
}
static char *
json_to_str(struct json_value *obj)
{
struct strbuf sb;
struct json_format fmt = {
.indent = 0,
.precision = 5,
.write = strbuf_writer,
.data = &sb
};
memset(&sb, 0, sizeof(sb));
json_value_format(obj, &fmt, 0);
strbuf_writer(&sb, "", 1);
if (sb.err) {
free(sb.base);
return NULL;
}
return sb.base;
}
static int
httpd_json_response(struct MHD_Connection *conn,
char const *url, char const *method,
int status,
struct json_value *val)
{
char *reply;
struct MHD_Response *resp;
int ret;
reply = json_to_str(val);
json_value_free(val);
if (!reply)
return http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
resp = MHD_create_response_from_buffer(strlen(reply),
reply, MHD_RESPMEM_MUST_COPY);
http_log(conn, method, url, status, reply);
free(reply);
MHD_add_response_header(resp,
MHD_HTTP_HEADER_CONTENT_TYPE,
"application/json");
ret = MHD_queue_response(conn, status, resp);
MHD_destroy_response(resp);
return ret;
}
static int
ept_config(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix,
struct json_value *unused)
{
struct json_value *val;
while (*suffix == '/')
suffix++;
val = config_to_json();
if (!val)
return http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
if (*suffix) {
struct json_value *jv, *cp;
int ret;
if (json_object_get(val, suffix, &jv)) {
ret = http_response(conn, method, url,
(errno == ENOENT)
? MHD_HTTP_NOT_FOUND
: MHD_HTTP_INTERNAL_SERVER_ERROR);
json_value_free(val);
return ret;
}
if (json_value_copy(jv, &cp)) {
ret = http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
json_value_free(val);
return ret;
}
json_value_free(val);
val = cp;
}
return httpd_json_response(conn, url, method, MHD_HTTP_OK, val);
}
static int
ept_ip_delete(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix,
struct json_value *unused)
{
int ret;
while (*suffix == '/')
suffix++;
if (!*suffix)
return http_response(conn, method, url, MHD_HTTP_FORBIDDEN);
switch (pinger_host_delete_by_name(suffix)) {
case UPD_OK:
ret = http_response(conn, method, url, MHD_HTTP_OK);
break;
case UPD_EXISTS:
ret = http_response_detail(conn, method, url,
MHD_HTTP_NOT_FOUND,
"No such host", 0);
break;
default:
ret = http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
}
return ret;
}
static int
ept_ip_put(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix,
struct json_value *unused)
{
int ret;
while (*suffix == '/')
suffix++;
if (!*suffix)
return http_response(conn, method, url, MHD_HTTP_FORBIDDEN);
switch (pinger_host_add_name(suffix)) {
case UPD_OK:
break;
case UPD_NORESOLV:
return http_response_detail(conn, method, url,
MHD_HTTP_FORBIDDEN,
"Hostname does not resolve",
0);
case UPD_NOMEM:
return http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
case UPD_EXISTS:
return http_response_detail(conn, method, url,
MHD_HTTP_FORBIDDEN,
"Hostname already exists",
0);
}
return http_response(conn, method, url, MHD_HTTP_CREATED);
}
static int
ept_ip_post(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix,
struct json_value *obj)
{
char const *err_text;
int err_pos;
int status;
status = pinger_hostlist_set(obj, &err_text, &err_pos);
if (!err_text && !err_pos)
return http_response(conn, method, url, status);
else
return http_response_detail(conn, method, url, status,
err_text, err_pos);
}
static int
ept_host_stat(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix,
struct json_value *unused)
{
struct json_value *val;
int rc, ret;
while (*suffix == '/')
suffix++;
if (suffix[0] == 0) {
rc = get_all_host_stat(&val);
} else {
rc = get_hostname_stat(suffix, &val);
}
switch (rc) {
case GET_OK:
ret = httpd_json_response(conn, url, method, MHD_HTTP_OK, val);
break;
case GET_NOENT:
ret = http_response_detail(conn, method, url,
MHD_HTTP_NOT_FOUND,
"No such host", 0);
break;
default:
ret = http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
}
return ret;
}
static int
ept_ip_stat(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix,
struct json_value *unused)
{
struct json_value *val;
int rc;
while (*suffix == '/')
suffix++;
if (suffix[0] == 0) {
return http_response(conn, method, url, MHD_HTTP_FORBIDDEN);
} else {
int ret;
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_protocol = IPPROTO_TCP;
rc = getaddrinfo(suffix, NULL, &hints, &res);
if (rc)
return http_response(conn, method, url,
MHD_HTTP_NOT_FOUND);
rc = get_ipaddr_stat(res->ai_addr, res->ai_addrlen, &val);
switch (rc) {
case GET_OK:
ret = httpd_json_response(conn, url, method,
MHD_HTTP_OK, val);
break;
case GET_NOENT:
ret = http_response_detail(conn, method, url,
MHD_HTTP_NOT_FOUND,
"No host matches IP", 0);
break;
default:
ret = http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
break;
}
freeaddrinfo(res);
return ret;
}
}
static int
ept_ip_match(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix,
struct json_value *unused)
{
struct json_value *val;
int rc;
int ret;
struct addrinfo hints, *res;
while (*suffix == '/')
suffix++;
if (suffix[0] == 0)
return http_response(conn, method, url, MHD_HTTP_FORBIDDEN);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_protocol = IPPROTO_TCP;
rc = getaddrinfo(suffix, NULL, &hints, &res);
if (rc)
return http_response(conn, method, url, MHD_HTTP_NOT_FOUND);
rc = get_host_matches(&res, &val);
if (rc)
ret = http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
else
ret = httpd_json_response(conn, url, method, MHD_HTTP_OK, val);
freeaddrinfo(res);
return ret;
}
struct auth_location {
char *url;
size_t ulen;
int wildcard;
char *method;
char *passwd_file;
char *realm;
struct auth_location *next;
};
static struct auth_location *auth_head, *auth_tail;
int
cf_auth(int mode, union cf_callback_arg *arg, void *data)
{
int ac;
char **av;
char *endp;
struct auth_location *loc;
if (mode != CF_PARSE)
return CF_RET_IGNORE;
// auth basic METHOD URL [FILE [REALM]]
enum { i_type, i_method, i_url, i_file, i_realm };
switch (strsplit(arg->input.val, 5, &ac, &av, &endp)) {
case STRSPLIT_OK:
if (!*endp)
break;
/* fall through */
case STRSPLIT_ERR:
error("%s:%d: syntax error near %s",
arg->input.file, arg->input.line, endp);
argcv_free(ac, av);
return CF_RET_FAIL;
case STRSPLIT_NOMEM:
emalloc_die();
}
if (ac < 3) {
error("%s:%d: syntax error",
arg->input.file, arg->input.line);
argcv_free(ac, av);
return CF_RET_FAIL;
}
if (!auth_head && ac < 5) {
error("%s:%d: realm or password file name missing",
arg->input.file, arg->input.line);
argcv_free(ac, av);
return CF_RET_FAIL;
}
if (strcmp(av[i_type], "basic")) {
error("%s:%d: unsupported authentication method",
arg->input.file, arg->input.line);
argcv_free(ac, av);
return CF_RET_FAIL;
}
loc = emalloc(sizeof(*loc));
loc->url = av[i_url];
loc->ulen = strlen(loc->url);
loc->wildcard = loc->url[strcspn(loc->url, "[]*?")] != 0;
loc->method = av[i_method];
loc->passwd_file = av[i_file] ? av[i_file] : auth_tail->passwd_file;
loc->realm = av[i_realm] ? av[i_realm] : auth_tail->realm;
loc->next = NULL;
if (auth_tail)
auth_tail->next = loc;
else
auth_head = loc;
auth_tail = loc;
/* All elements except 1st were transferred to loc, hence 1 as the
value of the first parameter. */
argcv_free(1, av);
return CF_RET_OK;
}
#define WWW_AUTH_PFX "Basic realm=\""
#define WWW_AUTH_SFX "\""
static char *
www_auth_hdr_format(char const *realm)
{
size_t len = strlen(realm);
char const *p;
char *buf, *q;
for (p = realm; *p; p++)
if (*p == '\\' || *p == '"')
len++;
len += sizeof(WWW_AUTH_PFX) + sizeof(WWW_AUTH_SFX) - 1;
buf = malloc(len);
if (buf) {
strcpy(buf, WWW_AUTH_PFX);
q = buf + sizeof(WWW_AUTH_PFX) - 1;
for (p = realm; *p; p++) {
if (*p == '\\' || *p == '"')
*q++ = '\\';
*q++ = *p;
}
strcpy(q, WWW_AUTH_SFX);
}
return buf;
}
static int
http_unauthorized(struct MHD_Connection *conn,
char const *method, char const *url,
char const *str)
{
int status = MHD_HTTP_UNAUTHORIZED;
int ret;
char *buf;
struct MHD_Response *resp =
MHD_create_response_from_buffer(0,
NULL,
MHD_RESPMEM_PERSISTENT);
buf = www_auth_hdr_format(str);
if (buf) {
MHD_add_response_header(resp,
MHD_HTTP_HEADER_WWW_AUTHENTICATE,
buf);
free(buf);
} else {
status = MHD_HTTP_INTERNAL_SERVER_ERROR;
}
http_log(conn, method, url, status, NULL);
ret = MHD_queue_response(conn, status, resp);
MHD_destroy_response(resp);
return ret;
}
static int
try_auth(struct MHD_Connection *conn, const char *url, const char *method,
int *ret_val)
{
struct auth_location *loc;
size_t url_len;
char *url_buf;
url_len = strcspn(url, "?");
while (url_len > 1 && url[url_len-1] == '/')
--url_len;
url_buf = malloc(url_len + 1);
if (!url_buf) {
*ret_val = http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
return 1;
}
memcpy(url_buf, url, url_len);
url_buf[url_len] = 0;
for (loc = auth_head; loc; loc = loc->next) {
if (fnmatch(loc->method, method, 0))
continue;
if (loc->wildcard
? fnmatch(loc->url, url_buf, 0) == 0
: (url_len >= loc->ulen
&& strncmp(url_buf, loc->url, loc->ulen) == 0
&& (url_buf[loc->ulen] == '/'
|| url_buf[loc->ulen] == 0))) {
char const *auth;
free(url_buf);
auth = MHD_lookup_connection_value(conn,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_AUTHORIZATION);
if (!auth) {
*ret_val = http_unauthorized(conn, method, url,
loc->realm);
return 1;
}
switch (basicauth(loc->passwd_file, auth)) {
case BASICAUTH_DENY:
case BASICAUTH_BAD_INPUT:
*ret_val = http_unauthorized(conn, method, url,
loc->realm);
return 1;
case BASICAUTH_ALLOW:
return 0;
case BASICAUTH_CANT_OPEN:
error("can't open password file %s: %s",
loc->passwd_file, strerror(errno));
/* fall through */
default:
*ret_val = http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
return 1;
}
}
}
free(url_buf);
return 0;
}
typedef int (*ENDPOINT_HANDLER)(struct MHD_Connection *,
const char *, const char *, const char *,
struct json_value *);
enum {
EPT_EXACT = 0x01,
EPT_PREFIX = 0x02
};
struct endpoint {
char *url;
int flags;
char *method;
ENDPOINT_HANDLER handler;
};
static struct endpoint endpoint[] = {
{ "/config/ip-list", EPT_PREFIX, MHD_HTTP_METHOD_PUT, ept_ip_put },
{ "/config/ip-list", EPT_PREFIX, MHD_HTTP_METHOD_POST, ept_ip_post },
{ "/config/ip-list", EPT_PREFIX, MHD_HTTP_METHOD_DELETE, ept_ip_delete },
{ "/config", EPT_PREFIX, MHD_HTTP_METHOD_GET, ept_config },
{ "/host", EPT_PREFIX, MHD_HTTP_METHOD_GET, ept_host_stat },
{ "/ip", EPT_PREFIX, MHD_HTTP_METHOD_GET, ept_ip_stat },
{ "/match", EPT_PREFIX, MHD_HTTP_METHOD_GET, ept_ip_match },
{ NULL }
};
static int
find_endpoint_handler(char const *url, char const *method,
ENDPOINT_HANDLER *ret_handler, char const **ret_suffix)
{
struct endpoint *ept;
int urlmatch = 0;
for (ept = endpoint; ept->url; ept++) {
if (ept->flags & EPT_EXACT) {
if (strcmp(ept->url, url) == 0) {
if (strcmp(ept->method, method)) {
urlmatch++;
continue;
}
*ret_handler = ept->handler;
*ret_suffix = url + strlen(url);
return MHD_HTTP_OK;
}
} else {
size_t len = strlen(ept->url);
if (strncmp(url, ept->url, len) == 0
&& (url[len] == '/' || url[len] == 0)) {
if (strcmp(ept->method, method)) {
urlmatch++;
continue;
}
*ret_handler = ept->handler;
*ret_suffix = url + len;
return MHD_HTTP_OK;
}
}
}
return urlmatch ? MHD_HTTP_METHOD_NOT_ALLOWED : MHD_HTTP_NOT_FOUND;
}
struct postbuf {
char *base;
size_t size;
size_t len;
ENDPOINT_HANDLER handler;
char suffix[1];
};
static void
postbuf_free(struct postbuf *pb)
{
free(pb->base);
free(pb);
}
static int
postbuf_add(struct postbuf *pb, const char *str, size_t len)
{
while (pb->len + len >= pb->size) {
char *np = json_2nrealloc(pb->base, &pb->size, 1);
if (!np)
return -1;
pb->base = np;
}
memcpy(pb->base + pb->len, str, len);
pb->len += len;
return 0;
}
static int
p903_httpd_handler(void *cls,
struct MHD_Connection *conn,
const char *url, const char *method,
const char *version,
const char *upload_data, size_t *upload_data_size,
void **con_cls)
{
ENDPOINT_HANDLER handler;
char const *suffix;
int status;
if (*con_cls == NULL && try_auth(conn, url, method, &status))
return status;
if (strcmp(method, MHD_HTTP_METHOD_POST) == 0) {
struct postbuf *pb;
if (*con_cls == NULL) {
char const *ctype;
status = find_endpoint_handler(url, method, &handler,
&suffix);
if (status != MHD_HTTP_OK)
return http_response(conn, method, url, status);
ctype = MHD_lookup_connection_value(conn,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_CONTENT_TYPE);
if (!ctype || strcmp(ctype, "application/json"))
return http_response(conn, method, url,
MHD_HTTP_NOT_ACCEPTABLE);
pb = malloc(sizeof(*pb) + strlen(suffix));
if (!pb)
return http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
pb->base = NULL;
pb->size = 0;
pb->len = 0;
pb->handler = handler;
strcpy(pb->suffix, suffix);
*con_cls = pb;
} else {
pb = *con_cls;
if (*upload_data_size != 0) {
if (postbuf_add(pb, upload_data,
*upload_data_size)) {
postbuf_free(pb);
return http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
}
*upload_data_size = 0;
} else {
char *endp;
int rc;
struct json_value *jv;
if (postbuf_add(pb, "", 1)) {
postbuf_free(pb);
return http_response(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
}
rc = json_parse_string(pb->base, &jv, &endp);
if (rc != JSON_E_NOERR) {
error("%s: %s near #%d", url,
json_strerror(rc), endp - pb->base);
return http_response(conn, method, url,
MHD_HTTP_BAD_REQUEST);
}
rc = pb->handler(conn, url, method, pb->suffix,
jv);
postbuf_free(pb);
json_value_free(jv);
return rc;
}
}
return MHD_YES;
}
/* For all other methods */
status = find_endpoint_handler(url, method, &handler, &suffix);
if (status != MHD_HTTP_OK)
return http_response(conn, method, url, status);
return handler(conn, url, method, suffix, NULL);
}
static void
sigign(int sig)
{
}
void
ping903(void)
{
struct MHD_Daemon *mhd;
sigset_t sigs;
int i;
pthread_t tid;
int fd = -1;
struct sockaddr *server_addr;
void *p;
struct sigaction act;
p903_init();
fd = open_listener(httpd_addr, &server_addr);
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = sigign;
/* Block the 'fatal signals' and SIGPIPE in the handling thread */
sigemptyset(&sigs);
for (i = 0; fatal_signals[i]; i++) {
sigaddset(&sigs, fatal_signals[i]);
sigaction(fatal_signals[i], &act, NULL);
}
sigaddset(&sigs, SIGPIPE);
pthread_sigmask(SIG_BLOCK, &sigs, NULL);
MHD_set_panic_func(p903_httpd_panic, NULL);
mhd = MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD
| MHD_USE_ERROR_LOG, 0,
p903_httpd_acl, server_addr,
p903_httpd_handler, NULL,
MHD_OPTION_LISTEN_SOCKET, fd,
MHD_OPTION_EXTERNAL_LOGGER, p903_httpd_logger, NULL,
MHD_OPTION_LISTEN_BACKLOG_SIZE, httpd_backlog_size,
MHD_OPTION_END);
/* Unblock only the fatal signals */
sigdelset(&sigs, SIGPIPE);
pthread_sigmask(SIG_UNBLOCK, &sigs, NULL);
/* Start the Receiver thread */
pthread_create(&tid, NULL, p903_receiver, NULL);
/* Start the Sender thread */
pthread_create(&tid, NULL, p903_sender, NULL);
/* Start the Scheduler thread */
pthread_create(&tid, NULL, p903_scheduler, NULL);
/* Wait for signal to arrive */
sigwait(&sigs, &i);
info("shutting down on signal %s", strsignal(i));
MHD_stop_daemon(mhd);
pthread_create(&tid, NULL, p903_saver, NULL);
pthread_join(tid, &p);
}