summaryrefslogtreecommitdiffstats
path: root/httpd
diff options
context:
space:
mode:
Diffstat (limited to 'httpd')
-rw-r--r--httpd/h2o_utils.c60
-rw-r--r--httpd/h2o_utils.h38
-rw-r--r--httpd/http_server.c339
-rw-r--r--httpd/http_server.h10
4 files changed, 447 insertions, 0 deletions
diff --git a/httpd/h2o_utils.c b/httpd/h2o_utils.c
new file mode 100644
index 000000000..943216f59
--- /dev/null
+++ b/httpd/h2o_utils.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "h2o_utils.h"
+
+#include "h2o/string_.h"
+
+#include "libnetdata/libnetdata.h"
+
+char *iovec_to_cstr(h2o_iovec_t *str)
+{
+ char *c_str = mallocz(str->len + 1);
+ memcpy(c_str, str->base, str->len);
+ c_str[str->len] = 0;
+ return c_str;
+}
+
+#define KEY_VAL_BUFFER_GROWTH_STEP 5
+h2o_iovec_pair_vector_t *parse_URL_params(h2o_mem_pool_t *pool, h2o_iovec_t params_string)
+{
+ h2o_iovec_pair_vector_t *params_vec = h2o_mem_alloc_shared(pool, sizeof(h2o_iovec_pair_vector_t), NULL);
+ memset(params_vec, 0, sizeof(h2o_iovec_pair_vector_t));
+
+ h2o_iovec_pair_t param;
+ while ((param.name.base = (char*)h2o_next_token(&params_string, '&', &param.name.len, &param.value)) != NULL) {
+ if (params_vec->capacity == params_vec->size)
+ h2o_vector_reserve(pool, params_vec, params_vec->capacity + KEY_VAL_BUFFER_GROWTH_STEP);
+
+ params_vec->entries[params_vec->size++] = param;
+ }
+
+ return params_vec;
+}
+
+h2o_iovec_pair_t *get_URL_param_by_name(h2o_iovec_pair_vector_t *params_vec, const void *needle, size_t needle_len)
+{
+ for (size_t i = 0; i < params_vec->size; i++) {
+ h2o_iovec_pair_t *ret = &params_vec->entries[i];
+ if (h2o_memis(ret->name.base, ret->name.len, needle, needle_len))
+ return ret;
+ }
+ return NULL;
+}
+
+char *url_unescape(const char *url)
+{
+ char *result = mallocz(strlen(url) + 1);
+
+ int i, j;
+ for (i = 0, j = 0; url[i] != 0; i++, j++) {
+ if (url[i] == '%' && isxdigit(url[i+1]) && isxdigit(url[i+2])) {
+ char hex[3] = { url[i+1], url[i+2], 0 };
+ result[j] = strtol(hex, NULL, 16);
+ i += 2;
+ } else
+ result[j] = url[i];
+ }
+ result[j] = 0;
+
+ return result;
+}
diff --git a/httpd/h2o_utils.h b/httpd/h2o_utils.h
new file mode 100644
index 000000000..6760ed9a9
--- /dev/null
+++ b/httpd/h2o_utils.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_H2O_UTILS_H
+#define NETDATA_H2O_UTILS_H
+
+#include "h2o/memory.h"
+
+#define __HAS_URL_PARAMS(reqptr) ((reqptr)->query_at != SIZE_MAX && ((reqptr)->path.len - (reqptr)->query_at > 1))
+#define IF_HAS_URL_PARAMS(reqptr) if __HAS_URL_PARAMS(reqptr)
+#define UNLESS_HAS_URL_PARAMS(reqptr) if (!__HAS_URL_PARAMS(reqptr))
+#define URL_PARAMS_IOVEC_INIT(reqptr) { .base = &(reqptr)->path.base[(reqptr)->query_at + 1], \
+ .len = (reqptr)->path.len - (reqptr)->query_at - 1 }
+#define URL_PARAMS_IOVEC_INIT_WITH_QUESTIONMARK(reqptr) { .base = &(reqptr)->path.base[(reqptr)->query_at], \
+ .len = (reqptr)->path.len - (reqptr)->query_at }
+
+#define PRINTF_H2O_IOVEC_FMT "%.*s"
+#define PRINTF_H2O_IOVEC(iovec) ((int)(iovec)->len), ((iovec)->base)
+
+char *iovec_to_cstr(h2o_iovec_t *str);
+
+typedef struct h2o_iovec_pair {
+ h2o_iovec_t name;
+ h2o_iovec_t value;
+} h2o_iovec_pair_t;
+
+typedef H2O_VECTOR(h2o_iovec_pair_t) h2o_iovec_pair_vector_t;
+
+// Takes the part of url behind ? (the url encoded parameters)
+// and parse it to vector of name/value pairs without copying the actual strings
+h2o_iovec_pair_vector_t *parse_URL_params(h2o_mem_pool_t *pool, h2o_iovec_t params_string);
+
+// Searches for parameter by name (provided in needle)
+// returns pointer to it or NULL
+h2o_iovec_pair_t *get_URL_param_by_name(h2o_iovec_pair_vector_t *params_vec, const void *needle, size_t needle_len);
+
+char *url_unescape(const char *url);
+
+#endif /* NETDATA_H2O_UTILS_H */
diff --git a/httpd/http_server.c b/httpd/http_server.c
new file mode 100644
index 000000000..24b168d92
--- /dev/null
+++ b/httpd/http_server.c
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "daemon/common.h"
+#include "http_server.h"
+#include "h2o.h"
+
+#include "h2o_utils.h"
+
+static h2o_globalconf_t config;
+static h2o_context_t ctx;
+static h2o_accept_ctx_t accept_ctx;
+
+#define CONTENT_JSON_UTF8 H2O_STRLIT("application/json; charset=utf-8")
+#define CONTENT_TEXT_UTF8 H2O_STRLIT("text/plain; charset=utf-8")
+#define NBUF_INITIAL_SIZE_RESP (4096)
+#define API_V1_PREFIX "/api/v1/"
+#define HOST_SELECT_PREFIX "/host/"
+
+#define HTTPD_CONFIG_SECTION "httpd"
+#define HTTPD_ENABLED_DEFAULT false
+
+static void on_accept(h2o_socket_t *listener, const char *err)
+{
+ h2o_socket_t *sock;
+
+ if (err != NULL) {
+ return;
+ }
+
+ if ((sock = h2o_evloop_socket_accept(listener)) == NULL)
+ return;
+ h2o_accept(&accept_ctx, sock);
+}
+
+static int create_listener(const char *ip, int port)
+{
+ struct sockaddr_in addr;
+ int fd, reuseaddr_flag = 1;
+ h2o_socket_t *sock;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = inet_addr(ip);
+ addr.sin_port = htons(port);
+
+ if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ||
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_flag, sizeof(reuseaddr_flag)) != 0 ||
+ bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0 || listen(fd, SOMAXCONN) != 0) {
+ return -1;
+ }
+
+ sock = h2o_evloop_socket_create(ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ);
+ h2o_socket_read_start(sock, on_accept);
+
+ return 0;
+}
+
+static int ssl_init()
+{
+ if (!config_get_boolean(HTTPD_CONFIG_SECTION, "ssl", false))
+ return 0;
+
+ char default_fn[FILENAME_MAX + 1];
+
+ snprintfz(default_fn, FILENAME_MAX, "%s/ssl/key.pem", netdata_configured_user_config_dir);
+ const char *key_fn = config_get(HTTPD_CONFIG_SECTION, "ssl key", default_fn);
+
+ snprintfz(default_fn, FILENAME_MAX, "%s/ssl/cert.pem", netdata_configured_user_config_dir);
+ const char *cert_fn = config_get(HTTPD_CONFIG_SECTION, "ssl certificate", default_fn);
+
+#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110
+ accept_ctx.ssl_ctx = SSL_CTX_new(SSLv23_server_method());
+#else
+ accept_ctx.ssl_ctx = SSL_CTX_new(TLS_server_method());
+#endif
+
+ SSL_CTX_set_options(accept_ctx.ssl_ctx, SSL_OP_NO_SSLv2);
+
+ /* load certificate and private key */
+ if (SSL_CTX_use_PrivateKey_file(accept_ctx.ssl_ctx, key_fn, SSL_FILETYPE_PEM) != 1) {
+ error("Could not load server key from \"%s\"", key_fn);
+ return -1;
+ }
+ if (SSL_CTX_use_certificate_file(accept_ctx.ssl_ctx, cert_fn, SSL_FILETYPE_PEM) != 1) {
+ error("Could not load certificate from \"%s\"", cert_fn);
+ return -1;
+ }
+
+ h2o_ssl_register_alpn_protocols(accept_ctx.ssl_ctx, h2o_http2_alpn_protocols);
+
+ info("SSL support enabled");
+
+ return 0;
+}
+
+// I did not find a way to do wildcard paths to make common handler for urls like:
+// /api/v1/info
+// /host/child/api/v1/info
+// /host/uuid/api/v1/info
+// ideally we could do something like "/*/api/v1/info" subscription
+// so we do it "manually" here with uberhandler
+static inline int _netdata_uberhandler(h2o_req_t *req, RRDHOST **host)
+{
+ if (!h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET")))
+ return -1;
+
+ static h2o_generator_t generator = { NULL, NULL };
+
+ h2o_iovec_t norm_path = req->path_normalized;
+
+ if (norm_path.len > strlen(HOST_SELECT_PREFIX) && !memcmp(norm_path.base, HOST_SELECT_PREFIX, strlen(HOST_SELECT_PREFIX))) {
+ h2o_iovec_t host_id; // host_id can be either and UUID or a hostname of the child
+
+ norm_path.base += strlen(HOST_SELECT_PREFIX);
+ norm_path.len -= strlen(HOST_SELECT_PREFIX);
+
+ host_id = norm_path;
+
+ size_t end_loc = h2o_strstr(host_id.base, host_id.len, "/", 1);
+ if (end_loc != SIZE_MAX) {
+ host_id.len = end_loc;
+ norm_path.base += end_loc;
+ norm_path.len -= end_loc;
+ }
+
+ char *c_host_id = iovec_to_cstr(&host_id);
+ *host = rrdhost_find_by_hostname(c_host_id);
+ if (!*host)
+ *host = rrdhost_find_by_guid(c_host_id);
+ if (!*host) {
+ req->res.status = HTTP_RESP_BAD_REQUEST;
+ req->res.reason = "Wrong host id";
+ h2o_send_inline(req, H2O_STRLIT("Host id provided was not found!\n"));
+ freez(c_host_id);
+ return 0;
+ }
+ freez(c_host_id);
+
+ // we have to rewrite URL here in case this is not an api call
+ // so that the subsequent file upload handler can send the correct
+ // files to the client
+ // if this is not an API call we will abort this handler later
+ // and let the internal serve file handler of h2o care for things
+
+ if (end_loc == SIZE_MAX) {
+ req->path.len = 1;
+ req->path_normalized.len = 1;
+ } else {
+ size_t offset = norm_path.base - req->path_normalized.base;
+ req->path.len -= offset;
+ req->path.base += offset;
+ req->query_at -= offset;
+ req->path_normalized.len -= offset;
+ req->path_normalized.base += offset;
+ }
+ }
+
+ // workaround for a dashboard bug which causes sometimes urls like
+ // "//api/v1/info" to be caled instead of "/api/v1/info"
+ if (norm_path.len > 2 &&
+ norm_path.base[0] == '/' &&
+ norm_path.base[1] == '/' ) {
+ norm_path.base++;
+ norm_path.len--;
+ }
+
+ size_t api_loc = h2o_strstr(norm_path.base, norm_path.len, H2O_STRLIT(API_V1_PREFIX));
+ if (api_loc == SIZE_MAX)
+ return 1;
+
+ h2o_iovec_t api_command = norm_path;
+ api_command.base += api_loc + strlen(API_V1_PREFIX);
+ api_command.len -= api_loc + strlen(API_V1_PREFIX);
+
+ if (!api_command.len)
+ return 1;
+
+ // this (emulating struct web_client) is a hack and will be removed
+ // in future PRs but needs bigger changes in old http_api_v1
+ // we need to make the web_client_api_request_v1 to be web server
+ // agnostic and remove the old webservers dependency creep into the
+ // individual response generators and thus remove the need to "emulate"
+ // the old webserver calling this function here and in ACLK
+ struct web_client w;
+ w.response.data = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL);
+ w.response.header = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL);
+ w.url_query_string_decoded = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL);
+ w.acl = WEB_CLIENT_ACL_DASHBOARD;
+
+ char *path_c_str = iovec_to_cstr(&api_command);
+ char *path_unescaped = url_unescape(path_c_str);
+ freez(path_c_str);
+
+ IF_HAS_URL_PARAMS(req) {
+ h2o_iovec_t query_params = URL_PARAMS_IOVEC_INIT_WITH_QUESTIONMARK(req);
+ char *query_c_str = iovec_to_cstr(&query_params);
+ char *query_unescaped = url_unescape(query_c_str);
+ freez(query_c_str);
+ buffer_strcat(w.url_query_string_decoded, query_unescaped);
+ freez(query_unescaped);
+ }
+
+ web_client_api_request_v1(*host, &w, path_unescaped);
+ freez(path_unescaped);
+
+ h2o_iovec_t body = buffer_to_h2o_iovec(w.response.data);
+
+ // we move msg body to req->pool managed memory as it has to
+ // live until whole response has been encrypted and sent
+ // when req is finished memory will be freed with the pool
+ void *managed = h2o_mem_alloc_shared(&req->pool, body.len, NULL);
+ memcpy(managed, body.base, body.len);
+ body.base = managed;
+
+ req->res.status = HTTP_RESP_OK;
+ req->res.reason = "OK";
+ if (w.response.data->content_type == CT_APPLICATION_JSON)
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, CONTENT_JSON_UTF8);
+ else
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, CONTENT_TEXT_UTF8);
+ h2o_start_response(req, &generator);
+ h2o_send(req, &body, 1, H2O_SEND_STATE_FINAL);
+
+ buffer_free(w.response.data);
+ buffer_free(w.response.header);
+ buffer_free(w.url_query_string_decoded);
+
+ return 0;
+}
+
+static int netdata_uberhandler(h2o_handler_t *self, h2o_req_t *req)
+{
+ UNUSED(self);
+ RRDHOST *host = localhost;
+
+ int ret = _netdata_uberhandler(req, &host);
+
+ char host_uuid_str[UUID_STR_LEN];
+ uuid_unparse_lower(host->host_uuid, host_uuid_str);
+
+ if (!ret) {
+ log_access("HTTPD OK method: " PRINTF_H2O_IOVEC_FMT
+ ", path: " PRINTF_H2O_IOVEC_FMT
+ ", as host: %s"
+ ", response: %d",
+ PRINTF_H2O_IOVEC(&req->method),
+ PRINTF_H2O_IOVEC(&req->input.path),
+ host == localhost ? "localhost" : host_uuid_str,
+ req->res.status);
+ } else {
+ log_access("HTTPD %d"
+ " method: " PRINTF_H2O_IOVEC_FMT
+ ", path: " PRINTF_H2O_IOVEC_FMT
+ ", forwarding to file handler as path: " PRINTF_H2O_IOVEC_FMT,
+ ret,
+ PRINTF_H2O_IOVEC(&req->method),
+ PRINTF_H2O_IOVEC(&req->input.path),
+ PRINTF_H2O_IOVEC(&req->path));
+ }
+
+ return ret;
+}
+
+static int hdl_netdata_conf(h2o_handler_t *self, h2o_req_t *req)
+{
+ UNUSED(self);
+ if (!h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET")))
+ return -1;
+
+ BUFFER *buf = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL);
+ config_generate(buf, 0);
+
+ void *managed = h2o_mem_alloc_shared(&req->pool, buf->len, NULL);
+ memcpy(managed, buf->buffer, buf->len);
+
+ req->res.status = HTTP_RESP_OK;
+ req->res.reason = "OK";
+ h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, CONTENT_TEXT_UTF8);
+ h2o_send_inline(req, managed, buf->len);
+ buffer_free(buf);
+
+ return 0;
+}
+
+#define POLL_INTERVAL 100
+
+void *httpd_main(void *ptr) {
+ struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
+
+ h2o_pathconf_t *pathconf;
+ h2o_hostconf_t *hostconf;
+
+ netdata_thread_disable_cancelability();
+
+ const char *bind_addr = config_get(HTTPD_CONFIG_SECTION, "bind to", "127.0.0.1");
+ int bind_port = config_get_number(HTTPD_CONFIG_SECTION, "port", 19998);
+
+ h2o_config_init(&config);
+ hostconf = h2o_config_register_host(&config, h2o_iovec_init(H2O_STRLIT("default")), bind_port);
+
+ pathconf = h2o_config_register_path(hostconf, "/netdata.conf", 0);
+ h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler));
+ handler->on_req = hdl_netdata_conf;
+
+ pathconf = h2o_config_register_path(hostconf, "/", 0);
+ handler = h2o_create_handler(pathconf, sizeof(*handler));
+ handler->on_req = netdata_uberhandler;
+ h2o_file_register(pathconf, netdata_configured_web_dir, NULL, NULL, H2O_FILE_FLAG_SEND_COMPRESSED);
+
+ h2o_context_init(&ctx, h2o_evloop_create(), &config);
+
+ if(ssl_init()) {
+ error_report("SSL was requested but could not be properly initialized. Aborting.");
+ return NULL;
+ }
+
+ accept_ctx.ctx = &ctx;
+ accept_ctx.hosts = config.hosts;
+
+ if (create_listener(bind_addr, bind_port) != 0) {
+ error("failed to create listener %s:%d", bind_addr, bind_port);
+ return NULL;
+ }
+
+ while (service_running(SERVICE_HTTPD)) {
+ int rc = h2o_evloop_run(ctx.loop, POLL_INTERVAL);
+ if (rc < 0 && errno != EINTR) {
+ error("h2o_evloop_run returned (%d) with errno other than EINTR. Aborting", rc);
+ break;
+ }
+ }
+
+ static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
+ return NULL;
+}
+
+int httpd_is_enabled() {
+ return config_get_boolean(HTTPD_CONFIG_SECTION, "enabled", HTTPD_ENABLED_DEFAULT);
+}
diff --git a/httpd/http_server.h b/httpd/http_server.h
new file mode 100644
index 000000000..23b78da83
--- /dev/null
+++ b/httpd/http_server.h
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef HTTP_SERVER_H
+#define HTTP_SERVER_H
+
+void *httpd_main(void * ptr);
+
+int httpd_is_enabled();
+
+#endif /* HTTP_SERVER_H */