From f36d14dbb8633d3228bd116b524ff5d13b3c87d3 Mon Sep 17 00:00:00 2001 From: Timotej S <6674623+underhood@users.noreply.github.com> Date: Fri, 19 Mar 2021 11:33:23 +0100 Subject: move https_client into separate file (#10784) --- Makefile.am | 2 + aclk/aclk_otp.c | 248 +--------------------------------------------------- aclk/https_client.c | 246 +++++++++++++++++++++++++++++++++++++++++++++++++++ aclk/https_client.h | 11 +++ 4 files changed, 260 insertions(+), 247 deletions(-) create mode 100644 aclk/https_client.c create mode 100644 aclk/https_client.h diff --git a/Makefile.am b/Makefile.am index 9ea6790979..180d337ce9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -555,6 +555,8 @@ ACLK_FILES = \ aclk/aclk_tx_msgs.h \ aclk/aclk_rx_msgs.c \ aclk/aclk_rx_msgs.h \ + aclk/https_client.c \ + aclk/https_client.h \ $(NULL) else #ACLK_NG ACLK_FILES = \ diff --git a/aclk/aclk_otp.c b/aclk/aclk_otp.c index 22ce746f61..fcb9d600cd 100644 --- a/aclk/aclk_otp.c +++ b/aclk/aclk_otp.c @@ -3,7 +3,7 @@ #include "aclk_otp.h" -#include "libnetdata/libnetdata.h" +#include "https_client.h" #include "../daemon/common.h" @@ -167,252 +167,6 @@ static int private_decrypt(RSA *p_key, unsigned char * enc_data, int data_len, u return result; } -typedef enum http_req_type { - HTTP_REQ_GET, - HTTP_REQ_POST -} http_req_type; - -enum http_parse_state { - HTTP_PARSE_INITIAL = 0, - HTTP_PARSE_HEADERS, - HTTP_PARSE_CONTENT -}; - -typedef struct { - enum http_parse_state state; - int content_length; - int http_code; -} http_parse_ctx; - -#define HTTP_PARSE_CTX_INITIALIZER { .state = HTTP_PARSE_INITIAL, .content_length = -1, .http_code = 0 } - -#define NEED_MORE_DATA 0 -#define PARSE_SUCCESS 1 -#define PARSE_ERROR -1 -#define HTTP_LINE_TERM "\x0D\x0A" -#define RESP_PROTO "HTTP/1.1 " -#define HTTP_KEYVAL_SEPARATOR ": " -#define HTTP_HDR_BUFFER_SIZE 256 -#define PORT_STR_MAX_BYTES 7 - -static void process_http_hdr(http_parse_ctx *parse_ctx, const char *key, const char *val) -{ - // currently we care only about content-length - // but in future the way this is written - // it can be extended - if (!strcmp("content-length", key)) { - parse_ctx->content_length = atoi(val); - } -} - -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)) { - error("CRLF expected"); - return 1; - } - - char *separator = rbuf_find_bytes(buf, HTTP_KEYVAL_SEPARATOR, strlen(HTTP_KEYVAL_SEPARATOR), &idx); - if (!separator) { - error("Missing Key/Value separator"); - return 1; - } - if (idx >= HTTP_HDR_BUFFER_SIZE) { - 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) { - error("Value of key \"%s\" too long", buf_key); - return 1; - } - - rbuf_pop(buf, buf_val, idx_end); - buf_val[idx_end] = 0; - - rbuf_bump_tail(buf, strlen(HTTP_KEYVAL_SEPARATOR)); - - for (ptr = buf_key; *ptr; ptr++) - *ptr = tolower(*ptr); - - process_http_hdr(parse_ctx, buf_key, buf_val); - - return 0; -} - -static int 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 NEED_MORE_DATA; - switch (parse_ctx->state) { - case HTTP_PARSE_INITIAL: - if (rbuf_memcmp_n(buf, RESP_PROTO, strlen(RESP_PROTO))) { - error("Expected response to start with \"%s\"", RESP_PROTO); - return PARSE_ERROR; - } - rbuf_bump_tail(buf, strlen(RESP_PROTO)); - if (rbuf_pop(buf, rc, 4) != 4) { - error("Expected HTTP status code"); - return PARSE_ERROR; - } - if (rc[3] != ' ') { - error("Expected space after HTTP return code"); - return PARSE_ERROR; - } - rc[3] = 0; - parse_ctx->http_code = atoi(rc); - if (parse_ctx->http_code < 100 || parse_ctx->http_code >= 600) { - error("HTTP code not in range 100 to 599"); - return 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 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: - if (parse_ctx->content_length < 0) { - error("content-length missing and http headers ended"); - return PARSE_ERROR; - } - if (rbuf_bytes_available(buf) >= (size_t)parse_ctx->content_length) - return PARSE_SUCCESS; - return NEED_MORE_DATA; - } - } while(1); -} - -static int https_request(http_req_type method, char *host, int port, char *url, char *b, size_t b_size, char *payload) -{ - struct timeval timeout = { .tv_sec = 30, .tv_usec = 0 }; - char sport[PORT_STR_MAX_BYTES]; - size_t len = 0; - int rc = 1; - int ret; - char *ptr; - http_parse_ctx parse_ctx = HTTP_PARSE_CTX_INITIALIZER; - - rbuf_t buffer = rbuf_create(b_size); - if (!buffer) - return 1; - - snprintf(sport, PORT_STR_MAX_BYTES, "%d", port); - - if (payload != NULL) - len = strlen(payload); - - snprintf( - b, - b_size, - "%s %s HTTP/1.1\r\nHost: %s\r\nAccept: application/json\r\nContent-length: %zu\r\nAccept-Language: en-us\r\n" - "User-Agent: Netdata/rocks\r\n\r\n", - (method == HTTP_REQ_GET ? "GET" : "POST"), url, host, len); - - if (payload != NULL) - strncat(b, payload, b_size - len); - - len = strlen(b); - - debug(D_ACLK, "Sending HTTPS req (%zu bytes): '%s'", len, b); - int sock = connect_to_this_ip46(IPPROTO_TCP, SOCK_STREAM, host, 0, sport, &timeout); - - if (unlikely(sock == -1)) { - error("Handshake failed"); - goto exit_buf; - } - - SSL_CTX *ctx = security_initialize_openssl_client(); - if (ctx==NULL) { - error("Cannot allocate SSL context"); - goto exit_sock; - } - // Certificate chain: not updating the stores - do we need private CA roots? - // Calls to SSL_CTX_load_verify_locations would go here. - SSL *ssl = SSL_new(ctx); - if (ssl==NULL) { - error("Cannot allocate SSL"); - goto exit_CTX; - } - SSL_set_fd(ssl, sock); - ret = SSL_connect(ssl); - if (ret != 1) { - error("SSL_connect() failed with err=%d", ret); - goto exit_SSL; - } - - ret = SSL_write(ssl, b, len); - if (ret <= 0) - { - error("SSL_write() failed with err=%d", ret); - goto exit_SSL; - } - - b[0] = 0; - - do { - ptr = rbuf_get_linear_insert_range(buffer, &len); - ret = SSL_read(ssl, ptr, len - 1); - if (ret) - rbuf_bump_head(buffer, ret); - if (ret <= 0) - { - error("No response available - SSL_read()=%d", ret); - goto exit_FULL; - } - } while (!(ret = parse_http_response(buffer, &parse_ctx))); - - if (ret != PARSE_SUCCESS) { - error("Error parsing HTTP response"); - goto exit_FULL; - } - - if (parse_ctx.http_code < 200 || parse_ctx.http_code >= 300) { - error("HTTP Response not Success (got %d)", parse_ctx.http_code); - goto exit_FULL; - } - - len = rbuf_pop(buffer, b, b_size); - b[MIN(len, b_size-1)] = 0; - - rc = 0; -exit_FULL: -exit_SSL: - SSL_free(ssl); -exit_CTX: - SSL_CTX_free(ctx); -exit_sock: - close(sock); -exit_buf: - rbuf_free(buffer); - return rc; -} - // aclk_get_mqtt_otp is slightly modified original code from @amoss void aclk_get_mqtt_otp(RSA *p_key, char *aclk_hostname, int port, char **mqtt_usr, char **mqtt_pass) { diff --git a/aclk/https_client.c b/aclk/https_client.c new file mode 100644 index 0000000000..1b9546d779 --- /dev/null +++ b/aclk/https_client.c @@ -0,0 +1,246 @@ +#include "libnetdata/libnetdata.h" + +#include "https_client.h" + +#include "../mqtt_websockets/c-rbuf/include/ringbuffer.h" + +enum http_parse_state { + HTTP_PARSE_INITIAL = 0, + HTTP_PARSE_HEADERS, + HTTP_PARSE_CONTENT +}; + +typedef struct { + enum http_parse_state state; + int content_length; + int http_code; +} http_parse_ctx; + +#define HTTP_PARSE_CTX_INITIALIZER { .state = HTTP_PARSE_INITIAL, .content_length = -1, .http_code = 0 } + +#define NEED_MORE_DATA 0 +#define PARSE_SUCCESS 1 +#define PARSE_ERROR -1 +#define HTTP_LINE_TERM "\x0D\x0A" +#define RESP_PROTO "HTTP/1.1 " +#define HTTP_KEYVAL_SEPARATOR ": " +#define HTTP_HDR_BUFFER_SIZE 256 +#define PORT_STR_MAX_BYTES 7 + +static void process_http_hdr(http_parse_ctx *parse_ctx, const char *key, const char *val) +{ + // currently we care only about content-length + // but in future the way this is written + // it can be extended + if (!strcmp("content-length", key)) { + parse_ctx->content_length = atoi(val); + } +} + +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)) { + error("CRLF expected"); + return 1; + } + + char *separator = rbuf_find_bytes(buf, HTTP_KEYVAL_SEPARATOR, strlen(HTTP_KEYVAL_SEPARATOR), &idx); + if (!separator) { + error("Missing Key/Value separator"); + return 1; + } + if (idx >= HTTP_HDR_BUFFER_SIZE) { + 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) { + error("Value of key \"%s\" too long", buf_key); + return 1; + } + + rbuf_pop(buf, buf_val, idx_end); + buf_val[idx_end] = 0; + + rbuf_bump_tail(buf, strlen(HTTP_KEYVAL_SEPARATOR)); + + for (ptr = buf_key; *ptr; ptr++) + *ptr = tolower(*ptr); + + process_http_hdr(parse_ctx, buf_key, buf_val); + + return 0; +} + +static int 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 NEED_MORE_DATA; + switch (parse_ctx->state) { + case HTTP_PARSE_INITIAL: + if (rbuf_memcmp_n(buf, RESP_PROTO, strlen(RESP_PROTO))) { + error("Expected response to start with \"%s\"", RESP_PROTO); + return PARSE_ERROR; + } + rbuf_bump_tail(buf, strlen(RESP_PROTO)); + if (rbuf_pop(buf, rc, 4) != 4) { + error("Expected HTTP status code"); + return PARSE_ERROR; + } + if (rc[3] != ' ') { + error("Expected space after HTTP return code"); + return PARSE_ERROR; + } + rc[3] = 0; + parse_ctx->http_code = atoi(rc); + if (parse_ctx->http_code < 100 || parse_ctx->http_code >= 600) { + error("HTTP code not in range 100 to 599"); + return 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 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: + if (parse_ctx->content_length < 0) { + error("content-length missing and http headers ended"); + return PARSE_ERROR; + } + if (rbuf_bytes_available(buf) >= (size_t)parse_ctx->content_length) + return PARSE_SUCCESS; + return NEED_MORE_DATA; + } + } while(1); +} + +int https_request(http_req_type_t method, char *host, int port, char *url, char *b, size_t b_size, char *payload) +{ + struct timeval timeout = { .tv_sec = 30, .tv_usec = 0 }; + char sport[PORT_STR_MAX_BYTES]; + size_t len = 0; + int rc = 1; + int ret; + char *ptr; + http_parse_ctx parse_ctx = HTTP_PARSE_CTX_INITIALIZER; + + rbuf_t buffer = rbuf_create(b_size); + if (!buffer) + return 1; + + snprintf(sport, PORT_STR_MAX_BYTES, "%d", port); + + if (payload != NULL) + len = strlen(payload); + + snprintf( + b, + b_size, + "%s %s HTTP/1.1\r\nHost: %s\r\nAccept: application/json\r\nContent-length: %zu\r\nAccept-Language: en-us\r\n" + "User-Agent: Netdata/rocks\r\n\r\n", + (method == HTTP_REQ_GET ? "GET" : "POST"), url, host, len); + + if (payload != NULL) + strncat(b, payload, b_size - len); + + len = strlen(b); + + debug(D_ACLK, "Sending HTTPS req (%zu bytes): '%s'", len, b); + int sock = connect_to_this_ip46(IPPROTO_TCP, SOCK_STREAM, host, 0, sport, &timeout); + + if (unlikely(sock == -1)) { + error("Handshake failed"); + goto exit_buf; + } + + SSL_CTX *ctx = security_initialize_openssl_client(); + if (ctx==NULL) { + error("Cannot allocate SSL context"); + goto exit_sock; + } + // Certificate chain: not updating the stores - do we need private CA roots? + // Calls to SSL_CTX_load_verify_locations would go here. + SSL *ssl = SSL_new(ctx); + if (ssl==NULL) { + error("Cannot allocate SSL"); + goto exit_CTX; + } + SSL_set_fd(ssl, sock); + ret = SSL_connect(ssl); + if (ret != 1) { + error("SSL_connect() failed with err=%d", ret); + goto exit_SSL; + } + + ret = SSL_write(ssl, b, len); + if (ret <= 0) + { + error("SSL_write() failed with err=%d", ret); + goto exit_SSL; + } + + b[0] = 0; + + do { + ptr = rbuf_get_linear_insert_range(buffer, &len); + ret = SSL_read(ssl, ptr, len - 1); + if (ret) + rbuf_bump_head(buffer, ret); + if (ret <= 0) + { + error("No response available - SSL_read()=%d", ret); + goto exit_FULL; + } + } while (!(ret = parse_http_response(buffer, &parse_ctx))); + + if (ret != PARSE_SUCCESS) { + error("Error parsing HTTP response"); + goto exit_FULL; + } + + if (parse_ctx.http_code < 200 || parse_ctx.http_code >= 300) { + error("HTTP Response not Success (got %d)", parse_ctx.http_code); + goto exit_FULL; + } + + len = rbuf_pop(buffer, b, b_size); + b[MIN(len, b_size-1)] = 0; + + rc = 0; +exit_FULL: +exit_SSL: + SSL_free(ssl); +exit_CTX: + SSL_CTX_free(ctx); +exit_sock: + close(sock); +exit_buf: + rbuf_free(buffer); + return rc; +} diff --git a/aclk/https_client.h b/aclk/https_client.h new file mode 100644 index 0000000000..0d2e0dba7e --- /dev/null +++ b/aclk/https_client.h @@ -0,0 +1,11 @@ +#ifndef NETDATA_HTTPS_CLIENT_H +#define NETDATA_HTTPS_CLIENT_H + +typedef enum http_req_type { + HTTP_REQ_GET, + HTTP_REQ_POST +} http_req_type_t; + +int https_request(http_req_type_t method, char *host, int port, char *url, char *b, size_t b_size, char *payload); + +#endif /* NETDATA_HTTPS_CLIENT_H */ -- cgit v1.2.3