/* 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 "ping903.h"
#include "json.h"
#include "defs.h"
char *httpd_addr;
int httpd_access_log = 0;
int httpd_log_verbose = 0;
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_error(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;
}
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,
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_error(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, MHD_HTTP_OK, reply);
free(reply);
MHD_add_response_header(resp,
MHD_HTTP_HEADER_CONTENT_TYPE,
"application/json");
ret = MHD_queue_response(conn, MHD_HTTP_OK, 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 *val;
while (*suffix == '/')
suffix++;
val = config_to_json();
if (!val)
return http_error(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_error(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_error(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, val);
}
static int
ept_ip_delete(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix)
{
int ret;
struct MHD_Response *resp;
while (*suffix == '/')
suffix++;
if (!*suffix)
return http_error(conn, method, url, MHD_HTTP_FORBIDDEN);
if (hostping_delete_by_name(suffix))
return http_error(conn, method, url, MHD_HTTP_NOT_FOUND);
resp = MHD_create_response_from_buffer(0,
NULL,
MHD_RESPMEM_PERSISTENT);
http_log(conn, method, url, MHD_HTTP_OK, NULL);
ret = MHD_queue_response(conn, MHD_HTTP_OK, resp);
MHD_destroy_response(resp);
return ret;
}
static int
ept_ip_put(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix)
{
int ret;
struct MHD_Response *resp;
while (*suffix == '/')
suffix++;
if (!*suffix)
return http_error(conn, method, url, MHD_HTTP_FORBIDDEN);
if (hostping_add_name(suffix))
//FIXME
return http_error(conn, method, url, MHD_HTTP_FORBIDDEN);
resp = MHD_create_response_from_buffer(0,
NULL,
MHD_RESPMEM_PERSISTENT);
http_log(conn, method, url, MHD_HTTP_CREATED, NULL);
ret = MHD_queue_response(conn, MHD_HTTP_CREATED, resp);
MHD_destroy_response(resp);
return ret;
}
static int
ept_host_stat(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix)
{
struct json_value *val;
int rc;
while (*suffix == '/')
suffix++;
if (suffix[0] == 0) {
rc = get_all_host_stat(&val);
} else {
rc = get_hostname_stat(suffix, &val);
}
if (rc)
return http_error(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
return httpd_json_response(conn, url, method, val);
}
static int
ept_ip_stat(struct MHD_Connection *conn,
const char *url, const char *method, const char *suffix)
{
struct json_value *val;
int rc;
while (*suffix == '/')
suffix++;
if (suffix[0] == 0) {
return http_error(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_error(conn, method, url,
MHD_HTTP_NOT_FOUND);
rc = get_ipaddr_stat(res->ai_addr, res->ai_addrlen, &val);
if (rc)
ret = http_error(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
else if (val)
ret = httpd_json_response(conn, url, method, val);
else
ret = http_error(conn, method, url, MHD_HTTP_FORBIDDEN);
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 *val;
int rc;
int ret;
struct addrinfo hints, *res;
while (*suffix == '/')
suffix++;
if (suffix[0] == 0)
return http_error(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_error(conn, method, url, MHD_HTTP_NOT_FOUND);
rc = get_host_matches(&res, &val);
if (rc)
ret = http_error(conn, method, url,
MHD_HTTP_INTERNAL_SERVER_ERROR);
else
ret = httpd_json_response(conn, url, method, val);
freeaddrinfo(res);
return ret;
}
typedef int (*ENDPOINT_HANDLER)(struct MHD_Connection *,
const char *, const char *, const char *);
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_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 ENDPOINT_HANDLER
find_endpoint_handler(char const *url, char const *method, char const **suffix)
{
struct endpoint *ept;
for (ept = endpoint; ept->url; ept++) {
if (strcmp(ept->method, method))
continue;
if (ept->flags & EPT_EXACT) {
if (strcmp(ept->url, url) == 0) {
*suffix = url + strlen(url);
return ept->handler;
}
} else {
size_t len = strlen(ept->url);
if (strncmp(url, ept->url, len) == 0
&& (url[len] == '/' || url[len] == 0)) {
*suffix = url + len;
return ept->handler;
}
}
}
return NULL;
}
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;
handler = find_endpoint_handler(url, method, &suffix);
if (handler)
return handler(conn, url, method, suffix);
if (strcmp(method, MHD_HTTP_METHOD_GET))
return http_error(conn, method, url,
MHD_HTTP_METHOD_NOT_ALLOWED);
return http_error(conn, method, url, MHD_HTTP_FORBIDDEN);
}
void
ping903(void)
{
struct MHD_Daemon *mhd;
sigset_t sigs;
int i;
pthread_t tid;
int fd = -1;
struct sockaddr *server_addr;
p903_init();
fd = open_listener(httpd_addr, &server_addr);
/* Block the 'fatal signals' and SIGPIPE in the handling thread */
sigemptyset(&sigs);
for (i = 0; fatal_signals[i]; i++)
sigaddset(&sigs, fatal_signals[i]);
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_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);
MHD_stop_daemon(mhd);
}