diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-07-24 09:54:23 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-07-24 09:54:44 +0000 |
commit | 836b47cb7e99a977c5a23b059ca1d0b5065d310e (patch) | |
tree | 1604da8f482d02effa033c94a84be42bc0c848c3 /aclk/https_client.c | |
parent | Releasing debian version 1.44.3-2. (diff) | |
download | netdata-836b47cb7e99a977c5a23b059ca1d0b5065d310e.tar.xz netdata-836b47cb7e99a977c5a23b059ca1d0b5065d310e.zip |
Merging upstream version 1.46.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'aclk/https_client.c')
-rw-r--r-- | aclk/https_client.c | 869 |
1 files changed, 0 insertions, 869 deletions
diff --git a/aclk/https_client.c b/aclk/https_client.c deleted file mode 100644 index 5385786b..00000000 --- a/aclk/https_client.c +++ /dev/null @@ -1,869 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "libnetdata/libnetdata.h" - -#include "https_client.h" - -#include "aclk_util.h" - -#include "daemon/global_statistics.h" - -#define DEFAULT_CHUNKED_RESPONSE_BUFFER_SIZE (4096) - -static const char *http_req_type_to_str(http_req_type_t req) { - switch (req) { - case HTTP_REQ_GET: - return "GET"; - case HTTP_REQ_POST: - return "POST"; - case HTTP_REQ_CONNECT: - return "CONNECT"; - default: - return "unknown"; - } -} - -#define TRANSFER_ENCODING_CHUNKED (-2) - -#define HTTP_PARSE_CTX_INITIALIZER { .state = HTTP_PARSE_INITIAL, .content_length = -1, .http_code = 0 } -void http_parse_ctx_create(http_parse_ctx *ctx) -{ - ctx->state = HTTP_PARSE_INITIAL; - ctx->content_length = -1; - ctx->http_code = 0; - ctx->headers = c_rhash_new(0); - ctx->flags = HTTP_PARSE_FLAGS_DEFAULT; -} - -void http_parse_ctx_destroy(http_parse_ctx *ctx) -{ - c_rhash_iter_t iter; - const char *key; - - c_rhash_iter_t_initialize(&iter); - while ( !c_rhash_iter_str_keys(ctx->headers, &iter, &key) ) { - void *val; - c_rhash_get_ptr_by_str(ctx->headers, key, &val); - freez(val); - } - - c_rhash_destroy(ctx->headers); -} - -#define POLL_TO_MS 100 - -#define HTTP_LINE_TERM "\x0D\x0A" -#define RESP_PROTO "HTTP/1.1 " -#define HTTP_KEYVAL_SEPARATOR ": " -#define HTTP_HDR_BUFFER_SIZE 1024 -#define PORT_STR_MAX_BYTES 12 - -static int process_http_hdr(http_parse_ctx *parse_ctx, const char *key, const char *val) -{ - // currently we care only about specific headers - // we can skip the rest - if (parse_ctx->content_length < 0 && !strcmp("content-length", key)) { - if (parse_ctx->content_length == TRANSFER_ENCODING_CHUNKED) { - netdata_log_error("Content-length and transfer-encoding: chunked headers are mutually exclusive"); - return 1; - } - if (parse_ctx->content_length != -1) { - netdata_log_error("Duplicate content-length header"); - return 1; - } - parse_ctx->content_length = str2u(val); - if (parse_ctx->content_length < 0) { - netdata_log_error("Invalid content-length %d", parse_ctx->content_length); - return 1; - } - return 0; - } - if (!strcmp("transfer-encoding", key)) { - if (!strcmp("chunked", val)) { - if (parse_ctx->content_length != -1) { - netdata_log_error("Content-length and transfer-encoding: chunked headers are mutually exclusive"); - return 1; - } - parse_ctx->content_length = TRANSFER_ENCODING_CHUNKED; - } - return 0; - } - char *val_cpy = strdupz(val); - c_rhash_insert_str_ptr(parse_ctx->headers, key, val_cpy); - return 0; -} - -const char *get_http_header_by_name(http_parse_ctx *ctx, const char *name) -{ - const char *ret; - if (c_rhash_get_ptr_by_str(ctx->headers, name, (void**)&ret)) - return NULL; - - return ret; -} - -static int parse_http_hdr(rbuf_t buf, http_parse_ctx *parse_ctx) -{ - int idx, idx_end; - char buf_key[HTTP_HDR_BUFFER_SIZE]; - char buf_val[HTTP_HDR_BUFFER_SIZE]; - char *ptr = buf_key; - if (!rbuf_find_bytes(buf, HTTP_LINE_TERM, strlen(HTTP_LINE_TERM), &idx_end)) { - netdata_log_error("CRLF expected"); - return 1; - } - - char *separator = rbuf_find_bytes(buf, HTTP_KEYVAL_SEPARATOR, strlen(HTTP_KEYVAL_SEPARATOR), &idx); - if (!separator) { - netdata_log_error("Missing Key/Value separator"); - return 1; - } - if (idx >= HTTP_HDR_BUFFER_SIZE) { - netdata_log_error("Key name is too long"); - return 1; - } - - rbuf_pop(buf, buf_key, idx); - buf_key[idx] = 0; - - rbuf_bump_tail(buf, strlen(HTTP_KEYVAL_SEPARATOR)); - idx_end -= strlen(HTTP_KEYVAL_SEPARATOR) + idx; - if (idx_end >= HTTP_HDR_BUFFER_SIZE) { - netdata_log_error("Value of key \"%s\" too long", buf_key); - return 1; - } - - rbuf_pop(buf, buf_val, idx_end); - buf_val[idx_end] = 0; - - for (ptr = buf_key; *ptr; ptr++) - *ptr = tolower(*ptr); - - if (process_http_hdr(parse_ctx, buf_key, buf_val)) - return 1; - - return 0; -} - -static inline void chunked_response_buffer_grow_by(http_parse_ctx *parse_ctx, size_t size) -{ - if (unlikely(parse_ctx->chunked_response_size == 0)) { - parse_ctx->chunked_response = mallocz(size); - parse_ctx->chunked_response_size = size; - return; - } - parse_ctx->chunked_response = reallocz((void *)parse_ctx->chunked_response, parse_ctx->chunked_response_size + size); - parse_ctx->chunked_response_size += size; -} - -static int process_chunked_content(rbuf_t buf, http_parse_ctx *parse_ctx) -{ - int idx; - size_t bytes_to_copy; - - do { - switch (parse_ctx->chunked_content_state) { - case CHUNKED_CONTENT_CHUNK_SIZE: - if (!rbuf_find_bytes(buf, HTTP_LINE_TERM, strlen(HTTP_LINE_TERM), &idx)) { - if (rbuf_bytes_available(buf) >= rbuf_get_capacity(buf)) - return HTTP_PARSE_ERROR; - return HTTP_PARSE_NEED_MORE_DATA; - } - if (idx == 0) { - parse_ctx->chunked_content_state = CHUNKED_CONTENT_FINAL_CRLF; - continue; - } - if (idx >= HTTP_HDR_BUFFER_SIZE) { - netdata_log_error("Chunk size is too long"); - return HTTP_PARSE_ERROR; - } - char buf_size[HTTP_HDR_BUFFER_SIZE]; - rbuf_pop(buf, buf_size, idx); - buf_size[idx] = 0; - long chunk_size = strtol(buf_size, NULL, 16); - if (chunk_size < 0 || chunk_size == LONG_MAX) { - netdata_log_error("Chunk size out of range"); - return HTTP_PARSE_ERROR; - } - parse_ctx->chunk_size = chunk_size; - if (parse_ctx->chunk_size == 0) { - if (errno == EINVAL) { - netdata_log_error("Invalid chunk size"); - return HTTP_PARSE_ERROR; - } - parse_ctx->chunked_content_state = CHUNKED_CONTENT_CHUNK_END_CRLF; - continue; - } - parse_ctx->chunk_got = 0; - chunked_response_buffer_grow_by(parse_ctx, parse_ctx->chunk_size); - rbuf_bump_tail(buf, strlen(HTTP_LINE_TERM)); - parse_ctx->chunked_content_state = CHUNKED_CONTENT_CHUNK_DATA; - // fallthrough - case CHUNKED_CONTENT_CHUNK_DATA: - if (!(bytes_to_copy = rbuf_bytes_available(buf))) - return HTTP_PARSE_NEED_MORE_DATA; - if (bytes_to_copy > parse_ctx->chunk_size - parse_ctx->chunk_got) - bytes_to_copy = parse_ctx->chunk_size - parse_ctx->chunk_got; - rbuf_pop(buf, parse_ctx->chunked_response + parse_ctx->chunked_response_written, bytes_to_copy); - parse_ctx->chunk_got += bytes_to_copy; - parse_ctx->chunked_response_written += bytes_to_copy; - if (parse_ctx->chunk_got != parse_ctx->chunk_size) - continue; - parse_ctx->chunked_content_state = CHUNKED_CONTENT_CHUNK_END_CRLF; - // fallthrough - case CHUNKED_CONTENT_FINAL_CRLF: - case CHUNKED_CONTENT_CHUNK_END_CRLF: - if (rbuf_bytes_available(buf) < strlen(HTTP_LINE_TERM)) - return HTTP_PARSE_NEED_MORE_DATA; - char buf_crlf[strlen(HTTP_LINE_TERM)]; - rbuf_pop(buf, buf_crlf, strlen(HTTP_LINE_TERM)); - if (memcmp(buf_crlf, HTTP_LINE_TERM, strlen(HTTP_LINE_TERM))) { - netdata_log_error("CRLF expected"); - return HTTP_PARSE_ERROR; - } - if (parse_ctx->chunked_content_state == CHUNKED_CONTENT_FINAL_CRLF) { - if (parse_ctx->chunked_response_size != parse_ctx->chunked_response_written) - netdata_log_error("Chunked response size mismatch"); - chunked_response_buffer_grow_by(parse_ctx, 1); - parse_ctx->chunked_response[parse_ctx->chunked_response_written] = 0; - return HTTP_PARSE_SUCCESS; - } - if (parse_ctx->chunk_size == 0) { - parse_ctx->chunked_content_state = CHUNKED_CONTENT_FINAL_CRLF; - continue; - } - parse_ctx->chunked_content_state = CHUNKED_CONTENT_CHUNK_SIZE; - continue; - } - } while(1); -} - -http_parse_rc parse_http_response(rbuf_t buf, http_parse_ctx *parse_ctx) -{ - int idx; - char rc[4]; - - do { - if (parse_ctx->state != HTTP_PARSE_CONTENT && !rbuf_find_bytes(buf, HTTP_LINE_TERM, strlen(HTTP_LINE_TERM), &idx)) - return HTTP_PARSE_NEED_MORE_DATA; - switch (parse_ctx->state) { - case HTTP_PARSE_INITIAL: - if (rbuf_memcmp_n(buf, RESP_PROTO, strlen(RESP_PROTO))) { - netdata_log_error("Expected response to start with \"%s\"", RESP_PROTO); - return HTTP_PARSE_ERROR; - } - rbuf_bump_tail(buf, strlen(RESP_PROTO)); - if (rbuf_pop(buf, rc, 4) != 4) { - netdata_log_error("Expected HTTP status code"); - return HTTP_PARSE_ERROR; - } - if (rc[3] != ' ') { - netdata_log_error("Expected space after HTTP return code"); - return HTTP_PARSE_ERROR; - } - rc[3] = 0; - parse_ctx->http_code = atoi(rc); - if (parse_ctx->http_code < 100 || parse_ctx->http_code >= 600) { - netdata_log_error("HTTP code not in range 100 to 599"); - return HTTP_PARSE_ERROR; - } - - rbuf_find_bytes(buf, HTTP_LINE_TERM, strlen(HTTP_LINE_TERM), &idx); - - rbuf_bump_tail(buf, idx + strlen(HTTP_LINE_TERM)); - - parse_ctx->state = HTTP_PARSE_HEADERS; - break; - case HTTP_PARSE_HEADERS: - if (!idx) { - parse_ctx->state = HTTP_PARSE_CONTENT; - rbuf_bump_tail(buf, strlen(HTTP_LINE_TERM)); - break; - } - if (parse_http_hdr(buf, parse_ctx)) - return HTTP_PARSE_ERROR; - rbuf_find_bytes(buf, HTTP_LINE_TERM, strlen(HTTP_LINE_TERM), &idx); - rbuf_bump_tail(buf, idx + strlen(HTTP_LINE_TERM)); - break; - case HTTP_PARSE_CONTENT: - // replies like CONNECT etc. do not have content - if (parse_ctx->content_length == TRANSFER_ENCODING_CHUNKED) - return process_chunked_content(buf, parse_ctx); - - if (parse_ctx->content_length < 0) - return HTTP_PARSE_SUCCESS; - - if (parse_ctx->flags & HTTP_PARSE_FLAG_DONT_WAIT_FOR_CONTENT) - return HTTP_PARSE_SUCCESS; - - if (rbuf_bytes_available(buf) >= (size_t)parse_ctx->content_length) - return HTTP_PARSE_SUCCESS; - return HTTP_PARSE_NEED_MORE_DATA; - } - } while(1); -} - -typedef struct https_req_ctx { - https_req_t *request; - - int sock; - rbuf_t buf_rx; - - struct pollfd poll_fd; - - SSL_CTX *ssl_ctx; - SSL *ssl; - - size_t written; - - int self_signed_allowed; - - http_parse_ctx parse_ctx; - - time_t req_start_time; -} https_req_ctx_t; - -static int https_req_check_timedout(https_req_ctx_t *ctx) { - if (now_realtime_sec() > ctx->req_start_time + ctx->request->timeout_s) { - netdata_log_error("request timed out"); - return 1; - } - return 0; -} - -static char *_ssl_err_tos(int err) -{ - switch(err){ - case SSL_ERROR_SSL: - return "SSL_ERROR_SSL"; - case SSL_ERROR_WANT_READ: - return "SSL_ERROR_WANT_READ"; - case SSL_ERROR_WANT_WRITE: - return "SSL_ERROR_WANT_WRITE"; - case SSL_ERROR_NONE: - return "SSL_ERROR_NONE"; - case SSL_ERROR_ZERO_RETURN: - return "SSL_ERROR_ZERO_RETURN"; - case SSL_ERROR_WANT_CONNECT: - return "SSL_ERROR_WANT_CONNECT"; - case SSL_ERROR_WANT_ACCEPT: - return "SSL_ERROR_WANT_ACCEPT"; - } - return "Unknown!!!"; -} - -static int socket_write_all(https_req_ctx_t *ctx, char *data, size_t data_len) { - ctx->written = 0; - ctx->poll_fd.events = POLLOUT; - - do { - int ret = poll(&ctx->poll_fd, 1, POLL_TO_MS); - if (ret < 0) { - netdata_log_error("poll error"); - return 1; - } - if (ret == 0) { - if (https_req_check_timedout(ctx)) { - netdata_log_error("Poll timed out"); - return 2; - } - continue; - } - - ret = write(ctx->sock, &data[ctx->written], data_len - ctx->written); - if (ret > 0) { - ctx->written += ret; - } else if (errno != EAGAIN && errno != EWOULDBLOCK) { - netdata_log_error("Error writing to socket"); - return 3; - } - } while (ctx->written < data_len); - - return 0; -} - -static int ssl_write_all(https_req_ctx_t *ctx, char *data, size_t data_len) { - ctx->written = 0; - ctx->poll_fd.events |= POLLOUT; - - do { - int ret = poll(&ctx->poll_fd, 1, POLL_TO_MS); - if (ret < 0) { - netdata_log_error("poll error"); - return 1; - } - if (ret == 0) { - if (https_req_check_timedout(ctx)) { - netdata_log_error("Poll timed out"); - return 2; - } - continue; - } - ctx->poll_fd.events = 0; - - ret = SSL_write(ctx->ssl, &data[ctx->written], data_len - ctx->written); - if (ret > 0) { - ctx->written += ret; - } else { - ret = SSL_get_error(ctx->ssl, ret); - switch (ret) { - case SSL_ERROR_WANT_READ: - ctx->poll_fd.events |= POLLIN; - break; - case SSL_ERROR_WANT_WRITE: - ctx->poll_fd.events |= POLLOUT; - break; - default: - netdata_log_error("SSL_write Err: %s", _ssl_err_tos(ret)); - return 3; - } - } - } while (ctx->written < data_len); - - return 0; -} - -static inline int https_client_write_all(https_req_ctx_t *ctx, char *data, size_t data_len) { - if (ctx->ssl_ctx) - return ssl_write_all(ctx, data, data_len); - return socket_write_all(ctx, data, data_len); -} - -static int read_parse_response(https_req_ctx_t *ctx) { - int ret; - char *ptr; - size_t size; - - ctx->poll_fd.events = POLLIN; - do { - ret = poll(&ctx->poll_fd, 1, POLL_TO_MS); - if (ret < 0) { - netdata_log_error("poll error"); - return 1; - } - if (ret == 0) { - if (https_req_check_timedout(ctx)) { - netdata_log_error("Poll timed out"); - return 2; - } - if (!ctx->ssl_ctx) - continue; - } - ctx->poll_fd.events = 0; - - do { - ptr = rbuf_get_linear_insert_range(ctx->buf_rx, &size); - - if (ctx->ssl_ctx) - ret = SSL_read(ctx->ssl, ptr, size); - else - ret = read(ctx->sock, ptr, size); - - if (ret > 0) { - rbuf_bump_head(ctx->buf_rx, ret); - } else { - if (ctx->ssl_ctx) { - ret = SSL_get_error(ctx->ssl, ret); - switch (ret) { - case SSL_ERROR_WANT_READ: - ctx->poll_fd.events |= POLLIN; - break; - case SSL_ERROR_WANT_WRITE: - ctx->poll_fd.events |= POLLOUT; - break; - default: - netdata_log_error("SSL_read Err: %s", _ssl_err_tos(ret)); - return 3; - } - } else { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - netdata_log_error("write error"); - return 3; - } - ctx->poll_fd.events |= POLLIN; - } - } - } while (ctx->poll_fd.events == 0 && rbuf_bytes_free(ctx->buf_rx) > 0); - } while (!(ret = parse_http_response(ctx->buf_rx, &ctx->parse_ctx))); - - if (ret != HTTP_PARSE_SUCCESS) { - netdata_log_error("Error parsing HTTP response"); - return 1; - } - - return 0; -} - -#define TX_BUFFER_SIZE 8192 -#define RX_BUFFER_SIZE (TX_BUFFER_SIZE*2) -static int handle_http_request(https_req_ctx_t *ctx) { - BUFFER *hdr = buffer_create(TX_BUFFER_SIZE, &netdata_buffers_statistics.buffers_aclk); - int rc = 0; - - http_parse_ctx_create(&ctx->parse_ctx); - - // Prepare data to send - switch (ctx->request->request_type) { - case HTTP_REQ_CONNECT: - buffer_strcat(hdr, "CONNECT "); - break; - case HTTP_REQ_GET: - buffer_strcat(hdr, "GET "); - break; - case HTTP_REQ_POST: - buffer_strcat(hdr, "POST "); - break; - default: - netdata_log_error("Unknown HTTPS request type!"); - rc = 1; - goto err_exit; - } - - if (ctx->request->request_type == HTTP_REQ_CONNECT) { - buffer_strcat(hdr, ctx->request->host); - buffer_sprintf(hdr, ":%d", ctx->request->port); - } else { - buffer_strcat(hdr, ctx->request->url); - } - - buffer_strcat(hdr, HTTP_1_1 HTTP_ENDL); - - //TODO Headers! - if (ctx->request->request_type != HTTP_REQ_CONNECT) { - buffer_sprintf(hdr, "Host: %s\x0D\x0A", ctx->request->host); - } - buffer_strcat(hdr, "User-Agent: Netdata/rocks newhttpclient\x0D\x0A"); - - if (ctx->request->request_type == HTTP_REQ_POST && ctx->request->payload && ctx->request->payload_size) { - buffer_sprintf(hdr, "Content-Length: %zu\x0D\x0A", ctx->request->payload_size); - } - if (ctx->request->proxy_username) { - size_t creds_plain_len = strlen(ctx->request->proxy_username) + strlen(ctx->request->proxy_password) + 1 /* ':' */; - char *creds_plain = callocz(1, creds_plain_len + 1); - char *ptr = creds_plain; - strcpy(ptr, ctx->request->proxy_username); - ptr += strlen(ctx->request->proxy_username); - *ptr++ = ':'; - strcpy(ptr, ctx->request->proxy_password); - - int creds_base64_len = (((4 * creds_plain_len / 3) + 3) & ~3); - // OpenSSL encoder puts newline every 64 output bytes - // we remove those but during encoding we need that space in the buffer - creds_base64_len += (1+(creds_base64_len/64)) * strlen("\n"); - char *creds_base64 = callocz(1, creds_base64_len + 1); - base64_encode_helper((unsigned char*)creds_base64, &creds_base64_len, (unsigned char*)creds_plain, creds_plain_len); - buffer_sprintf(hdr, "Proxy-Authorization: Basic %s\x0D\x0A", creds_base64); - freez(creds_plain); - } - - buffer_strcat(hdr, "\x0D\x0A"); - - // Send the request - if (https_client_write_all(ctx, hdr->buffer, hdr->len)) { - netdata_log_error("Couldn't write HTTP request header into SSL connection"); - rc = 2; - goto err_exit; - } - - if (ctx->request->request_type == HTTP_REQ_POST && ctx->request->payload && ctx->request->payload_size) { - if (https_client_write_all(ctx, ctx->request->payload, ctx->request->payload_size)) { - netdata_log_error("Couldn't write payload into SSL connection"); - rc = 3; - goto err_exit; - } - } - - // Read The Response - if (read_parse_response(ctx)) { - netdata_log_error("Error reading or parsing response from server"); - if (ctx->parse_ctx.chunked_response) - freez(ctx->parse_ctx.chunked_response); - rc = 4; - goto err_exit; - } - -err_exit: - buffer_free(hdr); - return rc; -} - -static int cert_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) -{ - X509 *err_cert; - int err, depth; - char *err_str; - - if (!preverify_ok) { - err = X509_STORE_CTX_get_error(ctx); - depth = X509_STORE_CTX_get_error_depth(ctx); - err_cert = X509_STORE_CTX_get_current_cert(ctx); - err_str = X509_NAME_oneline(X509_get_subject_name(err_cert), NULL, 0); - - netdata_log_error("Cert Chain verify error:num=%d:%s:depth=%d:%s", err, - X509_verify_cert_error_string(err), depth, err_str); - - free(err_str); - } - -#ifdef ACLK_SSL_ALLOW_SELF_SIGNED - if (!preverify_ok && err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) - { - preverify_ok = 1; - netdata_log_error("Self Signed Certificate Accepted as the agent was built with ACLK_SSL_ALLOW_SELF_SIGNED"); - } -#endif - - return preverify_ok; -} - -int https_request(https_req_t *request, https_req_response_t *response) { - int rc = 1, ret; - char connect_port_str[PORT_STR_MAX_BYTES]; - - const char *connect_host = request->proxy_host ? request->proxy_host : request->host; - int connect_port = request->proxy_host ? request->proxy_port : request->port; - struct timeval timeout = { .tv_sec = request->timeout_s, .tv_usec = 0 }; - - https_req_ctx_t *ctx = callocz(1, sizeof(https_req_ctx_t)); - ctx->req_start_time = now_realtime_sec(); - - ctx->buf_rx = rbuf_create(RX_BUFFER_SIZE); - if (!ctx->buf_rx) { - netdata_log_error("Couldn't allocate buffer for RX data"); - goto exit_req_ctx; - } - - snprintfz(connect_port_str, PORT_STR_MAX_BYTES, "%d", connect_port); - - ctx->sock = connect_to_this_ip46(IPPROTO_TCP, SOCK_STREAM, connect_host, 0, connect_port_str, &timeout); - if (ctx->sock < 0) { - netdata_log_error("Error connecting TCP socket to \"%s\"", connect_host); - goto exit_buf_rx; - } - - if (fcntl(ctx->sock, F_SETFL, fcntl(ctx->sock, F_GETFL, 0) | O_NONBLOCK) == -1) { - netdata_log_error("Error setting O_NONBLOCK to TCP socket."); - goto exit_sock; - } - - ctx->poll_fd.fd = ctx->sock; - - // Do the CONNECT if proxy is used - if (request->proxy_host) { - https_req_t req = HTTPS_REQ_T_INITIALIZER; - req.request_type = HTTP_REQ_CONNECT; - req.timeout_s = request->timeout_s; - req.host = request->host; - req.port = request->port; - req.url = request->url; - req.proxy_username = request->proxy_username; - req.proxy_password = request->proxy_password; - ctx->request = &req; - if (handle_http_request(ctx)) { - netdata_log_error("Failed to CONNECT with proxy"); - http_parse_ctx_destroy(&ctx->parse_ctx); - goto exit_sock; - } - if (ctx->parse_ctx.http_code != 200) { - netdata_log_error("Proxy didn't return 200 OK (got %d)", ctx->parse_ctx.http_code); - http_parse_ctx_destroy(&ctx->parse_ctx); - goto exit_sock; - } - http_parse_ctx_destroy(&ctx->parse_ctx); - netdata_log_info("Proxy accepted CONNECT upgrade"); - } - ctx->request = request; - - ctx->ssl_ctx = netdata_ssl_create_client_ctx(0); - if (ctx->ssl_ctx==NULL) { - netdata_log_error("Cannot allocate SSL context"); - goto exit_sock; - } - - if (!SSL_CTX_set_default_verify_paths(ctx->ssl_ctx)) { - netdata_log_error("Error setting default verify paths"); - goto exit_CTX; - } - SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, cert_verify_callback); - - ctx->ssl = SSL_new(ctx->ssl_ctx); - if (ctx->ssl==NULL) { - netdata_log_error("Cannot allocate SSL"); - goto exit_CTX; - } - - if (!SSL_set_tlsext_host_name(ctx->ssl, connect_host)) { - netdata_log_error("Error setting TLS SNI host"); - goto exit_CTX; - } - - SSL_set_fd(ctx->ssl, ctx->sock); - ret = SSL_connect(ctx->ssl); - if (ret != -1 && ret != 1) { - netdata_log_error("SSL could not connect"); - goto exit_SSL; - } - if (ret == -1) { - // expected as underlying socket is non blocking! - // consult SSL_connect documentation for details - int ec = SSL_get_error(ctx->ssl, ret); - if (ec != SSL_ERROR_WANT_READ && ec != SSL_ERROR_WANT_WRITE) { - netdata_log_error("Failed to start SSL connection"); - goto exit_SSL; - } - } - - // The actual request here - if (handle_http_request(ctx)) { - netdata_log_error("Couldn't process request"); - http_parse_ctx_destroy(&ctx->parse_ctx); - goto exit_SSL; - } - http_parse_ctx_destroy(&ctx->parse_ctx); - response->http_code = ctx->parse_ctx.http_code; - if (ctx->parse_ctx.content_length == TRANSFER_ENCODING_CHUNKED) { - response->payload_size = ctx->parse_ctx.chunked_response_size; - response->payload = ctx->parse_ctx.chunked_response; - } - if (ctx->parse_ctx.content_length > 0) { - response->payload_size = ctx->parse_ctx.content_length; - response->payload = mallocz(response->payload_size + 1); - ret = rbuf_pop(ctx->buf_rx, response->payload, response->payload_size); - if (ret != (int)response->payload_size) { - netdata_log_error("Payload size doesn't match remaining data on the buffer!"); - response->payload_size = ret; - } - // normally we take payload as it is and copy it - // but for convenience in cases where payload is sth. like - // json we add terminating zero so that user of the data - // doesn't have to convert to C string (0 terminated) - // other uses still have correct payload_size and can copy - // only exact data without affixed 0x00 - ((char*)response->payload)[response->payload_size] = 0; // mallocz(response->payload_size + 1); - } - netdata_log_info("HTTPS \"%s\" request to \"%s\" finished with HTTP code: %d", http_req_type_to_str(ctx->request->request_type), ctx->request->host, response->http_code); - - rc = 0; - -exit_SSL: - SSL_free(ctx->ssl); -exit_CTX: - SSL_CTX_free(ctx->ssl_ctx); -exit_sock: - close(ctx->sock); -exit_buf_rx: - rbuf_free(ctx->buf_rx); -exit_req_ctx: - freez(ctx); - return rc; -} - -void https_req_response_free(https_req_response_t *res) { - freez(res->payload); -} - -void https_req_response_init(https_req_response_t *res) { - res->http_code = 0; - res->payload = NULL; - res->payload_size = 0; -} - -static inline char *UNUSED_FUNCTION(min_non_null)(char *a, char *b) { - if (!a) - return b; - if (!b) - return a; - return (a < b ? a : b); -} - -#define URI_PROTO_SEPARATOR "://" -#define URL_PARSER_LOG_PREFIX "url_parser " - -static int parse_host_port(url_t *url) { - char *ptr = strrchr(url->host, ':'); - if (ptr) { - size_t port_len = strlen(ptr + 1); - if (!port_len) { - netdata_log_error(URL_PARSER_LOG_PREFIX ": specified but no port number"); - return 1; - } - if (port_len > 5 /* MAX port length is 5digit long in decimal */) { - netdata_log_error(URL_PARSER_LOG_PREFIX "port # is too long"); - return 1; - } - *ptr = 0; - if (!strlen(url->host)) { - netdata_log_error(URL_PARSER_LOG_PREFIX "host empty after removing port"); - return 1; - } - url->port = atoi (ptr + 1); - } - return 0; -} - -static inline void port_by_proto(url_t *url) { - if (url->port) - return; - if (!url->proto) - return; - if (!strcmp(url->proto, "http")) { - url->port = 80; - return; - } - if (!strcmp(url->proto, "https")) { - url->port = 443; - return; - } -} - -#define STRDUPZ_2PTR(dest, start, end) \ - { \ - dest = mallocz(1 + end - start); \ - memcpy(dest, start, end - start); \ - dest[end - start] = 0; \ - } - -int url_parse(const char *url, url_t *parsed) { - const char *start = url; - const char *end = strstr(url, URI_PROTO_SEPARATOR); - - if (end) { - if (end == start) { - netdata_log_error(URL_PARSER_LOG_PREFIX "found " URI_PROTO_SEPARATOR " without protocol specified"); - return 1; - } - - STRDUPZ_2PTR(parsed->proto, start, end) - start = end + strlen(URI_PROTO_SEPARATOR); - } - - end = strchr(start, '/'); - if (!end) - end = start + strlen(start); - - if (start == end) { - netdata_log_error(URL_PARSER_LOG_PREFIX "Host empty"); - return 1; - } - - STRDUPZ_2PTR(parsed->host, start, end); - - if (parse_host_port(parsed)) - return 1; - - if (!*end) { - parsed->path = strdupz("/"); - port_by_proto(parsed); - return 0; - } - - parsed->path = strdupz(end); - port_by_proto(parsed); - return 0; -} - -void url_t_destroy(url_t *url) { - freez(url->host); - freez(url->path); - freez(url->proto); -} |