diff options
Diffstat (limited to 'src/ping903.c')
-rw-r--r-- | src/ping903.c | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/src/ping903.c b/src/ping903.c new file mode 100644 index 0000000..f98983b --- /dev/null +++ b/src/ping903.c @@ -0,0 +1,409 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <syslog.h> +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <microhttpd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include "ping903.h" +#include "json.h" + +#ifndef DEFAULT_ADDRESS +# define DEFAULT_ADDRESS "0.0.0.0" +#endif +#ifndef DEFAULT_SERVICE +# define DEFAULT_SERVICE "8080" +#endif + +char *httpd_addr; + +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]; + + 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) +{ + extern void *json_2nrealloc(void *p, size_t *pn, size_t s); + 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; +} + +int +ept_config(struct MHD_Connection *conn, + const char *url, const char *method, const char *suffix) +{ + struct json_value *val; + + val = config_to_json(); + if (!val) + return http_error(conn, method, url, + MHD_HTTP_INTERNAL_SERVER_ERROR); + return httpd_json_response(conn, url, method, val); +} + +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); +} + + +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", EPT_EXACT, MHD_HTTP_METHOD_GET, ept_config }, + { "/host", EPT_PREFIX, MHD_HTTP_METHOD_GET, ept_host_stat }, + { 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, +#if 0 + MHD_OPTION_NOTIFY_COMPLETED, p903_httpd_request_completed, NULL, +#endif + 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); + + pthread_create(&tid, NULL, p903_scheduler, NULL); + /* Start Sender thread */ + pthread_create(&tid, NULL, p903_sender, NULL); + /* Start Receiver thread */ + pthread_create(&tid, NULL, p903_receiver, NULL); + + /* Wait for signal to arrive */ + sigwait(&sigs, &i); + MHD_stop_daemon(mhd); +} |