aboutsummaryrefslogtreecommitdiff
path: root/src/ping903.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ping903.c')
-rw-r--r--src/ping903.c409
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);
+}

Return to:

Send suggestions and report system problems to the System administrator.