/* 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); }