@cryptotaxi247 / netdata-1 / commits / d7212e031

implements new https client for ACLK (#10805)

New HTTPS client for Agent/Cloud New Arch

Timotej S committed Apr 19, 2021 at 11:14 UTC d7212e031da9d1f827a64b7ba9e22c30df72b8e5
6 files changed +562 -163
aclk/aclk.c
+2 -42
@@ -420,45 +420,6 @@ static int aclk_block_till_recon_allowed() {
420 return 0;
421 }
422
423 -#define HTTP_PROXY_PREFIX "http://"
424 -static void set_proxy(struct mqtt_wss_proxy *out)
425 -{
426 - ACLK_PROXY_TYPE pt;
427 - const char *ptr = aclk_get_proxy(&pt);
428 - char *tmp;
429 - char *host;
430 - if (pt != PROXY_TYPE_HTTP)
431 - return;
432 -
433 - out->port = 0;
434 -
435 - if (!strncmp(ptr, HTTP_PROXY_PREFIX, strlen(HTTP_PROXY_PREFIX)))
436 - ptr += strlen(HTTP_PROXY_PREFIX);
437 -
438 - if ((tmp = strchr(ptr, '@')))
439 - ptr = tmp;
440 -
441 - if ((tmp = strchr(ptr, '/'))) {
442 - host = mallocz((tmp - ptr) + 1);
443 - memcpy(host, ptr, (tmp - ptr));
444 - host[tmp - ptr] = 0;
445 - } else
446 - host = strdupz(ptr);
447 -
448 - if ((tmp = strchr(host, ':'))) {
449 - *tmp = 0;
450 - tmp++;
451 - out->port = atoi(tmp);
452 - }
453 -
454 - if (out->port <= 0 || out->port > 65535)
455 - out->port = 8080;
456 -
457 - out->host = host;
458 -
459 - out->type = MQTT_WSS_PROXY_HTTP;
460 -}
461 -
423 /* Attempts to make a connection to MQTT broker over WSS
424 * @param client instance of mqtt_wss_client
425 * @return 0 - Successfull Connection,
@@ -500,9 +461,8 @@ static int aclk_attempt_to_connect(mqtt_wss_client client)
461 continue;
462 }
463
503 - struct mqtt_wss_proxy proxy_conf;
504 - proxy_conf.type = MQTT_WSS_DIRECT;
505 - set_proxy(&proxy_conf);
464 + struct mqtt_wss_proxy proxy_conf = { .host = NULL, .port = 0, .type = MQTT_WSS_DIRECT };
465 + aclk_set_proxy((char**)&proxy_conf.host, &proxy_conf.port, &proxy_conf.type);
466
467 struct mqtt_connect_params mqtt_conn_params = {
468 .clientid = "anon",
aclk/aclk_otp.c
+81 -42
@@ -9,6 +9,12 @@
9
10 #include "../mqtt_websockets/c-rbuf/include/ringbuffer.h"
11
12 +// CentOS 7 has older version that doesn't define this
13 +// same goes for MacOS
14 +#ifndef UUID_STR_LEN
15 +#define UUID_STR_LEN 37
16 +#endif
17 +
18 struct dictionary_singleton {
19 char *key;
20 char *result;
@@ -167,54 +173,74 @@ static int private_decrypt(RSA *p_key, unsigned char * enc_data, int data_len, u
173 return result;
174 }
175
170 -// aclk_get_mqtt_otp is slightly modified original code from @amoss
171 -void aclk_get_mqtt_otp(RSA *p_key, char *aclk_hostname, int port, char **mqtt_usr, char **mqtt_pass)
172 -{
173 - char *data_buffer = mallocz(NETDATA_WEB_RESPONSE_INITIAL_SIZE);
174 - debug(D_ACLK, "Performing challenge-response sequence");
175 - if (*mqtt_pass != NULL)
176 - {
177 - freez(*mqtt_pass);
178 - *mqtt_pass = NULL;
176 +static int aclk_https_request(https_req_t *request, https_req_response_t *response) {
177 + int rc;
178 + // wrapper for ACLK only which loads ACLK specific proxy settings
179 + // then only calls https_request
180 + struct mqtt_wss_proxy proxy_conf = { .host = NULL, .port = 0, .type = MQTT_WSS_DIRECT };
181 + aclk_set_proxy((char**)&proxy_conf.host, &proxy_conf.port, &proxy_conf.type);
182 +
183 + if (proxy_conf.type == MQTT_WSS_PROXY_HTTP) {
184 + request->proxy_host = (char*)proxy_conf.host; // TODO make it const as well
185 + request->proxy_port = proxy_conf.port;
186 }
180 - // curl http://cloud-iam-agent-service:8080/api/v1/auth/node/00000000-0000-0000-0000-000000000000/challenge
181 - // TODO - target host?
187 +
188 + rc = https_request(request, response);
189 + freez((char*)proxy_conf.host);
190 + return rc;
191 +}
192 +
193 +#define OTP_URL_PREFIX "/api/v1/auth/node/"
194 +void aclk_get_mqtt_otp(RSA *p_key, char *aclk_hostname, int port, char **mqtt_usr, char **mqtt_pass) {
195 + BUFFER *url = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20);
196 +
197 + https_req_t req = HTTPS_REQ_T_INITIALIZER;
198 + https_req_response_t resp = HTTPS_REQ_RESPONSE_T_INITIALIZER;
199 +
200 char *agent_id = is_agent_claimed();
201 if (agent_id == NULL)
202 {
203 error("Agent was not claimed - cannot perform challenge/response");
186 - goto CLEANUP;
204 + goto cleanup;
205 }
188 - char url[1024];
189 - sprintf(url, "/api/v1/auth/node/%s/challenge", agent_id);
190 - info("Retrieving challenge from cloud: %s %d %s", aclk_hostname, port, url);
191 - if (https_request(HTTP_REQ_GET, aclk_hostname, port, url, data_buffer, NETDATA_WEB_RESPONSE_INITIAL_SIZE, NULL))
192 - {
193 - error("Challenge failed: %s", data_buffer);
194 - goto CLEANUP;
206 +
207 + // GET Challenge
208 + req.host = aclk_hostname;
209 + req.port = port;
210 + buffer_sprintf(url, "%s%s/challenge", OTP_URL_PREFIX, agent_id);
211 + req.url = url->buffer;
212 +
213 + if (aclk_https_request(&req, &resp)) {
214 + error ("ACLK_OTP Challenge failed");
215 + goto cleanup;
216 + }
217 + if (resp.http_code != 200) {
218 + error ("ACLK_OTP Challenge HTTP code not 200 OK (got %d)", resp.http_code);
219 + goto cleanup_resp;
220 }
221 + info ("ACLK_OTP Got Challenge from Cloud");
222 +
223 struct dictionary_singleton challenge = { .key = "challenge", .result = NULL };
224
198 - debug(D_ACLK, "Challenge response from cloud: %s", data_buffer);
199 - if (json_parse(data_buffer, &challenge, json_extract_singleton) != JSON_OK)
225 + if (json_parse(resp.payload, &challenge, json_extract_singleton) != JSON_OK)
226 {
227 freez(challenge.result);
202 - error("Could not parse the json response with the challenge: %s", data_buffer);
203 - goto CLEANUP;
228 + error("Could not parse the the challenge");
229 + goto cleanup_resp;
230 }
231 if (challenge.result == NULL) {
206 - error("Could not retrieve challenge from auth response: %s", data_buffer);
207 - goto CLEANUP;
232 + error("Could not retrieve challenge JSON key from challenge response");
233 + goto cleanup_resp;
234 }
235
210 -
236 + // Decrypt the Challenge and Calculate Response
237 size_t challenge_len = strlen(challenge.result);
238 unsigned char decoded[512];
239 size_t decoded_len = base64_decode((unsigned char*)challenge.result, challenge_len, decoded, sizeof(decoded));
240 + freez(challenge.result);
241
242 unsigned char plaintext[4096]={};
243 int decrypted_length = private_decrypt(p_key, decoded, decoded_len, plaintext);
217 - freez(challenge.result);
244 char encoded[512];
245 size_t encoded_len = base64_encode(plaintext, decrypted_length, encoded, sizeof(encoded));
246 encoded[encoded_len] = 0;
@@ -223,27 +249,39 @@ void aclk_get_mqtt_otp(RSA *p_key, char *aclk_hostname, int port, char **mqtt_us
249 char response_json[4096]={};
250 sprintf(response_json, "{\"response\":\"%s\"}", encoded);
251 debug(D_ACLK, "Password phase: %s",response_json);
226 - // TODO - host
227 - sprintf(url, "/api/v1/auth/node/%s/password", agent_id);
228 - if (https_request(HTTP_REQ_POST, aclk_hostname, port, url, data_buffer, NETDATA_WEB_RESPONSE_INITIAL_SIZE, response_json))
229 - {
230 - error("Challenge-response failed: %s", data_buffer);
231 - goto CLEANUP;
232 - }
252
234 - debug(D_ACLK, "Password response from cloud: %s", data_buffer);
253 + https_req_response_free(&resp);
254 + https_req_response_init(&resp);
255 +
256 + // POST password
257 + req.request_type = HTTP_REQ_POST;
258 + buffer_flush(url);
259 + buffer_sprintf(url, "%s%s/password", OTP_URL_PREFIX, agent_id);
260 + req.url = url->buffer;
261 + req.payload = response_json;
262 + req.payload_size = strlen(response_json);
263 +
264 + if (aclk_https_request(&req, &resp)) {
265 + error ("ACLK_OTP Password error trying to post result to password");
266 + goto cleanup;
267 + }
268 + if (resp.http_code != 201) {
269 + error ("ACLK_OTP Password HTTP code not 201 Created (got %d)", resp.http_code);
270 + goto cleanup_resp;
271 + }
272 + info ("ACLK_OTP Got Password from Cloud");
273
274 struct dictionary_singleton password = { .key = "password", .result = NULL };
237 - if (json_parse(data_buffer, &password, json_extract_singleton) != JSON_OK)
275 + if (json_parse(resp.payload, &password, json_extract_singleton) != JSON_OK)
276 {
277 freez(password.result);
240 - error("Could not parse the json response with the password: %s", data_buffer);
241 - goto CLEANUP;
278 + error("Could not parse the json response with the password");
279 + goto cleanup_resp;
280 }
281
282 if (password.result == NULL ) {
283 error("Could not retrieve password from auth response");
246 - goto CLEANUP;
284 + goto cleanup_resp;
285 }
286 if (*mqtt_pass != NULL )
287 freez(*mqtt_pass);
@@ -253,9 +291,10 @@ void aclk_get_mqtt_otp(RSA *p_key, char *aclk_hostname, int port, char **mqtt_us
291 *mqtt_usr = agent_id;
292 agent_id = NULL;
293
256 -CLEANUP:
294 +cleanup_resp:
295 + https_req_response_free(&resp);
296 +cleanup:
297 if (agent_id != NULL)
298 freez(agent_id);
259 - freez(data_buffer);
260 - return;
299 + buffer_free(url);
300 }
aclk/aclk_util.c
+40
@@ -345,3 +345,43 @@ const char *aclk_get_proxy(ACLK_PROXY_TYPE *type)
345 *type = proxy_type;
346 return proxy;
347 }
348 +
349 +#define HTTP_PROXY_PREFIX "http://"
350 +void aclk_set_proxy(char **ohost, int *port, enum mqtt_wss_proxy_type *type)
351 +{
352 + ACLK_PROXY_TYPE pt;
353 + const char *ptr = aclk_get_proxy(&pt);
354 + char *tmp;
355 + char *host;
356 + if (pt != PROXY_TYPE_HTTP)
357 + return;
358 +
359 + *port = 0;
360 +
361 + if (!strncmp(ptr, HTTP_PROXY_PREFIX, strlen(HTTP_PROXY_PREFIX)))
362 + ptr += strlen(HTTP_PROXY_PREFIX);
363 +
364 + if ((tmp = strchr(ptr, '@')))
365 + ptr = tmp;
366 +
367 + if ((tmp = strchr(ptr, '/'))) {
368 + host = mallocz((tmp - ptr) + 1);
369 + memcpy(host, ptr, (tmp - ptr));
370 + host[tmp - ptr] = 0;
371 + } else
372 + host = strdupz(ptr);
373 +
374 + if ((tmp = strchr(host, ':'))) {
375 + *tmp = 0;
376 + tmp++;
377 + *port = atoi(tmp);
378 + }
379 +
380 + if (*port <= 0 || *port > 65535)
381 + *port = 8080;
382 +
383 + *ohost = host;
384 +
385 + if (type)
386 + *type = MQTT_WSS_PROXY_HTTP;
387 +}
aclk/aclk_util.h
+3
@@ -3,6 +3,7 @@
3 #define ACLK_UTIL_H
4
5 #include "libnetdata/libnetdata.h"
6 +#include "mqtt_wss_client.h"
7
8 // Helper stuff which should not have any further inside ACLK dependency
9 // and are supposed not to be needed outside of ACLK
@@ -49,4 +50,6 @@ void safe_log_proxy_censor(char *proxy);
50 int aclk_decode_base_url(char *url, char **aclk_hostname, int *aclk_port);
51 const char *aclk_get_proxy(ACLK_PROXY_TYPE *type);
52
53 +void aclk_set_proxy(char **ohost, int *port, enum mqtt_wss_proxy_type *type);
54 +
55 #endif /* ACLK_UTIL_H */
aclk/https_client.c
+382 -76
@@ -1,3 +1,5 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 #include "libnetdata/libnetdata.h"
4
5 #include "https_client.h"
@@ -10,6 +12,19 @@ enum http_parse_state {
12 HTTP_PARSE_CONTENT
13 };
14
15 +static const char *http_req_type_to_str(http_req_type_t req) {
16 + switch (req) {
17 + case HTTP_REQ_GET:
18 + return "GET";
19 + case HTTP_REQ_POST:
20 + return "POST";
21 + case HTTP_REQ_CONNECT:
22 + return "CONNECT";
23 + default:
24 + return "unknown";
25 + }
26 +}
27 +
28 typedef struct {
29 enum http_parse_state state;
30 int content_length;
@@ -17,6 +32,13 @@ typedef struct {
32 } http_parse_ctx;
33
34 #define HTTP_PARSE_CTX_INITIALIZER { .state = HTTP_PARSE_INITIAL, .content_length = -1, .http_code = 0 }
35 +static inline void http_parse_ctx_clear(http_parse_ctx *ctx) {
36 + ctx->state = HTTP_PARSE_INITIAL;
37 + ctx->content_length = -1;
38 + ctx->http_code = 0;
39 +}
40 +
41 +#define POLL_TO_MS 100
42
43 #define NEED_MORE_DATA 0
44 #define PARSE_SUCCESS 1
@@ -71,8 +93,6 @@ static int parse_http_hdr(rbuf_t buf, http_parse_ctx *parse_ctx)
93 rbuf_pop(buf, buf_val, idx_end);
94 buf_val[idx_end] = 0;
95
74 - rbuf_bump_tail(buf, strlen(HTTP_KEYVAL_SEPARATOR));
75 -
96 for (ptr = buf_key; *ptr; ptr++)
97 *ptr = tolower(*ptr);
98
@@ -129,10 +149,10 @@ static int parse_http_response(rbuf_t buf, http_parse_ctx *parse_ctx)
149 rbuf_bump_tail(buf, idx + strlen(HTTP_LINE_TERM));
150 break;
151 case HTTP_PARSE_CONTENT:
132 - if (parse_ctx->content_length < 0) {
133 - error("content-length missing and http headers ended");
134 - return PARSE_ERROR;
135 - }
152 + // replies like CONNECT etc. do not have content
153 + if (parse_ctx->content_length < 0)
154 + return PARSE_SUCCESS;
155 +
156 if (rbuf_bytes_available(buf) >= (size_t)parse_ctx->content_length)
157 return PARSE_SUCCESS;
158 return NEED_MORE_DATA;
@@ -140,107 +160,393 @@ static int parse_http_response(rbuf_t buf, http_parse_ctx *parse_ctx)
160 } while(1);
161 }
162
143 -int https_request(http_req_type_t method, char *host, int port, char *url, char *b, size_t b_size, char *payload)
163 +typedef struct https_req_ctx {
164 + https_req_t *request;
165 +
166 + int sock;
167 + rbuf_t buf_rx;
168 +
169 + struct pollfd poll_fd;
170 +
171 + SSL_CTX *ssl_ctx;
172 + SSL *ssl;
173 +
174 + size_t written;
175 +
176 + int self_signed_allowed;
177 +
178 + http_parse_ctx parse_ctx;
179 +
180 + time_t req_start_time;
181 +} https_req_ctx_t;
182 +
183 +static int https_req_check_timedout(https_req_ctx_t *ctx) {
184 + if (now_realtime_sec() > ctx->req_start_time + ctx->request->timeout_s) {
185 + error("request timed out");
186 + return 1;
187 + }
188 + return 0;
189 +}
190 +
191 +static char *_ssl_err_tos(int err)
192 {
145 - struct timeval timeout = { .tv_sec = 30, .tv_usec = 0 };
146 - char sport[PORT_STR_MAX_BYTES];
147 - size_t len = 0;
148 - int rc = 1;
193 + switch(err){
194 + case SSL_ERROR_SSL:
195 + return "SSL_ERROR_SSL";
196 + case SSL_ERROR_WANT_READ:
197 + return "SSL_ERROR_WANT_READ";
198 + case SSL_ERROR_WANT_WRITE:
199 + return "SSL_ERROR_WANT_WRITE";
200 + case SSL_ERROR_NONE:
201 + return "SSL_ERROR_NONE";
202 + case SSL_ERROR_ZERO_RETURN:
203 + return "SSL_ERROR_ZERO_RETURN";
204 + case SSL_ERROR_WANT_CONNECT:
205 + return "SSL_ERROR_WANT_CONNECT";
206 + case SSL_ERROR_WANT_ACCEPT:
207 + return "SSL_ERROR_WANT_ACCEPT";
208 + }
209 + return "Unknown!!!";
210 +}
211 +
212 +static int socket_write_all(https_req_ctx_t *ctx, char *data, size_t data_len) {
213 + ctx->written = 0;
214 + ctx->poll_fd.events = POLLOUT;
215 +
216 + do {
217 + int ret = poll(&ctx->poll_fd, 1, POLL_TO_MS);
218 + if (ret < 0) {
219 + error("poll error");
220 + return 1;
221 + }
222 + if (ret == 0) {
223 + if (https_req_check_timedout(ctx)) {
224 + error("Poll timed out");
225 + return 2;
226 + }
227 + continue;
228 + }
229 +
230 + ret = write(ctx->sock, &data[ctx->written], data_len - ctx->written);
231 + if (ret > 0) {
232 + ctx->written += ret;
233 + } else if (errno != EAGAIN && errno != EWOULDBLOCK) {
234 + error("Error writing to socket");
235 + return 3;
236 + }
237 + } while (ctx->written < data_len);
238 +
239 + return 0;
240 +}
241 +
242 +static int ssl_write_all(https_req_ctx_t *ctx, char *data, size_t data_len) {
243 + ctx->written = 0;
244 + ctx->poll_fd.events |= POLLOUT;
245 +
246 + do {
247 + int ret = poll(&ctx->poll_fd, 1, POLL_TO_MS);
248 + if (ret < 0) {
249 + error("poll error");
250 + return 1;
251 + }
252 + if (ret == 0) {
253 + if (https_req_check_timedout(ctx)) {
254 + error("Poll timed out");
255 + return 2;
256 + }
257 + continue;
258 + }
259 + ctx->poll_fd.events = 0;
260 +
261 + ret = SSL_write(ctx->ssl, &data[ctx->written], data_len - ctx->written);
262 + if (ret > 0) {
263 + ctx->written += ret;
264 + } else {
265 + ret = SSL_get_error(ctx->ssl, ret);
266 + switch (ret) {
267 + case SSL_ERROR_WANT_READ:
268 + ctx->poll_fd.events |= POLLIN;
269 + break;
270 + case SSL_ERROR_WANT_WRITE:
271 + ctx->poll_fd.events |= POLLOUT;
272 + break;
273 + default:
274 + error("SSL_write Err: %s", _ssl_err_tos(ret));
275 + return 3;
276 + }
277 + }
278 + } while (ctx->written < data_len);
279 +
280 + return 0;
281 +}
282 +
283 +static inline int https_client_write_all(https_req_ctx_t *ctx, char *data, size_t data_len) {
284 + if (ctx->ssl_ctx)
285 + return ssl_write_all(ctx, data, data_len);
286 + return socket_write_all(ctx, data, data_len);
287 +}
288 +
289 +static int read_parse_response(https_req_ctx_t *ctx) {
290 int ret;
291 char *ptr;
151 - http_parse_ctx parse_ctx = HTTP_PARSE_CTX_INITIALIZER;
292 + size_t size;
293
153 - rbuf_t buffer = rbuf_create(b_size);
154 - if (!buffer)
294 + ctx->poll_fd.events = POLLIN;
295 + do {
296 + ret = poll(&ctx->poll_fd, 1, POLL_TO_MS);
297 + if (ret < 0) {
298 + error("poll error");
299 + return 1;
300 + }
301 + if (ret == 0) {
302 + if (https_req_check_timedout(ctx)) {
303 + error("Poll timed out");
304 + return 2;
305 + }
306 + continue;
307 + }
308 + ctx->poll_fd.events = 0;
309 +
310 + ptr = rbuf_get_linear_insert_range(ctx->buf_rx, &size);
311 +
312 + if (ctx->ssl_ctx)
313 + ret = SSL_read(ctx->ssl, ptr, size);
314 + else
315 + ret = read(ctx->sock, ptr, size);
316 +
317 + if (ret > 0) {
318 + rbuf_bump_head(ctx->buf_rx, ret);
319 + } else {
320 + if (ctx->ssl_ctx) {
321 + ret = SSL_get_error(ctx->ssl, ret);
322 + switch (ret) {
323 + case SSL_ERROR_WANT_READ:
324 + ctx->poll_fd.events |= POLLIN;
325 + break;
326 + case SSL_ERROR_WANT_WRITE:
327 + ctx->poll_fd.events |= POLLOUT;
328 + break;
329 + default:
330 + error("SSL_read Err: %s", _ssl_err_tos(ret));
331 + return 3;
332 + }
333 + } else {
334 + if (errno != EAGAIN && errno != EWOULDBLOCK) {
335 + error("write error");
336 + return 3;
337 + }
338 + ctx->poll_fd.events |= POLLIN;
339 + }
340 + }
341 + } while (!(ret = parse_http_response(ctx->buf_rx, &ctx->parse_ctx)));
342 +
343 + if (ret != PARSE_SUCCESS) {
344 + error("Error parsing HTTP response");
345 return 1;
346 + }
347 +
348 + return 0;
349 +}
350
157 - snprintf(sport, PORT_STR_MAX_BYTES, "%d", port);
351 +#define TX_BUFFER_SIZE 8192
352 +#define RX_BUFFER_SIZE (TX_BUFFER_SIZE*2)
353 +static int handle_http_request(https_req_ctx_t *ctx) {
354 + BUFFER *hdr = buffer_create(TX_BUFFER_SIZE);
355 + int rc = 0;
356 +
357 + http_parse_ctx_clear(&ctx->parse_ctx);
358 +
359 + // Prepare data to send
360 + switch (ctx->request->request_type) {
361 + case HTTP_REQ_CONNECT:
362 + buffer_strcat(hdr, "CONNECT ");
363 + break;
364 + case HTTP_REQ_GET:
365 + buffer_strcat(hdr, "GET ");
366 + break;
367 + case HTTP_REQ_POST:
368 + buffer_strcat(hdr, "POST ");
369 + break;
370 + default:
371 + error("Unknown HTTPS request type!");
372 + rc = 1;
373 + goto err_exit;
374 + }
375
159 - if (payload != NULL)
160 - len = strlen(payload);
376 + if (ctx->request->request_type == HTTP_REQ_CONNECT) {
377 + buffer_strcat(hdr, ctx->request->host);
378 + buffer_sprintf(hdr, ":%d", ctx->request->port);
379 + } else {
380 + buffer_strcat(hdr, ctx->request->url);
381 + }
382
162 - snprintf(
163 - b,
164 - b_size,
165 - "%s %s HTTP/1.1\r\nHost: %s\r\nAccept: application/json\r\nContent-length: %zu\r\nAccept-Language: en-us\r\n"
166 - "User-Agent: Netdata/rocks\r\n\r\n",
167 - (method == HTTP_REQ_GET ? "GET" : "POST"), url, host, len);
383 + buffer_strcat(hdr, " HTTP/1.1\x0D\x0A");
384
169 - if (payload != NULL)
170 - strncat(b, payload, b_size - len);
385 + //TODO Headers!
386 + if (ctx->request->request_type != HTTP_REQ_CONNECT) {
387 + buffer_sprintf(hdr, "Host: %s\x0D\x0A", ctx->request->host);
388 + }
389 + buffer_strcat(hdr, "User-Agent: Netdata/rocks newhttpclient\x0D\x0A");
390
172 - len = strlen(b);
391 + if (ctx->request->request_type == HTTP_REQ_POST && ctx->request->payload && ctx->request->payload_size) {
392 + buffer_sprintf(hdr, "Content-Length: %zu\x0D\x0A", ctx->request->payload_size);
393 + }
394
174 - debug(D_ACLK, "Sending HTTPS req (%zu bytes): '%s'", len, b);
175 - int sock = connect_to_this_ip46(IPPROTO_TCP, SOCK_STREAM, host, 0, sport, &timeout);
395 + buffer_strcat(hdr, "\x0D\x0A");
396
177 - if (unlikely(sock == -1)) {
178 - error("Handshake failed");
179 - goto exit_buf;
397 + // Send the request
398 + if (https_client_write_all(ctx, hdr->buffer, hdr->len)) {
399 + error("Couldn't write HTTP request header into SSL connection");
400 + rc = 2;
401 + goto err_exit;
402 }
403
182 - SSL_CTX *ctx = security_initialize_openssl_client();
183 - if (ctx==NULL) {
184 - error("Cannot allocate SSL context");
185 - goto exit_sock;
404 + if (ctx->request->request_type == HTTP_REQ_POST && ctx->request->payload && ctx->request->payload_size) {
405 + if (https_client_write_all(ctx, ctx->request->payload, ctx->request->payload_size)) {
406 + error("Couldn't write payload into SSL connection");
407 + rc = 3;
408 + goto err_exit;
409 + }
410 }
187 - // Certificate chain: not updating the stores - do we need private CA roots?
188 - // Calls to SSL_CTX_load_verify_locations would go here.
189 - SSL *ssl = SSL_new(ctx);
190 - if (ssl==NULL) {
191 - error("Cannot allocate SSL");
192 - goto exit_CTX;
411 +
412 + // Read The Response
413 + if (read_parse_response(ctx)) {
414 + error("Error reading or parsing response from server");
415 + rc = 4;
416 + goto err_exit;
417 }
194 - SSL_set_fd(ssl, sock);
195 - ret = SSL_connect(ssl);
196 - if (ret != 1) {
197 - error("SSL_connect() failed with err=%d", ret);
198 - goto exit_SSL;
418 +
419 +err_exit:
420 + buffer_free(hdr);
421 + return rc;
422 +}
423 +
424 +int https_request(https_req_t *request, https_req_response_t *response) {
425 + int rc = 1, ret;
426 + char connect_port_str[PORT_STR_MAX_BYTES];
427 +
428 + const char *connect_host = request->proxy_host ? request->proxy_host : request->host;
429 + int connect_port = request->proxy_host ? request->proxy_port : request->port;
430 + struct timeval timeout = { .tv_sec = request->timeout_s, .tv_usec = 0 };
431 +
432 + https_req_ctx_t *ctx = callocz(1, sizeof(https_req_ctx_t));
433 + ctx->req_start_time = now_realtime_sec();
434 +
435 + ctx->buf_rx = rbuf_create(RX_BUFFER_SIZE);
436 + if (!ctx->buf_rx) {
437 + error("Couldn't allocate buffer for RX data");
438 + goto exit_req_ctx;
439 }
440
201 - ret = SSL_write(ssl, b, len);
202 - if (ret <= 0)
203 - {
204 - error("SSL_write() failed with err=%d", ret);
205 - goto exit_SSL;
441 + snprintf(connect_port_str, PORT_STR_MAX_BYTES, "%d", connect_port);
442 +
443 + ctx->sock = connect_to_this_ip46(IPPROTO_TCP, SOCK_STREAM, connect_host, 0, connect_port_str, &timeout);
444 + if (ctx->sock < 0) {
445 + error("Error connecting TCP socket to \"%s\"", connect_host);
446 + goto exit_buf_rx;
447 }
448
208 - b[0] = 0;
449 + if (fcntl(ctx->sock, F_SETFL, fcntl(ctx->sock, F_GETFL, 0) | O_NONBLOCK) == -1) {
450 + error("Error setting O_NONBLOCK to TCP socket.");
451 + goto exit_sock;
452 + }
453
210 - do {
211 - ptr = rbuf_get_linear_insert_range(buffer, &len);
212 - ret = SSL_read(ssl, ptr, len - 1);
213 - if (ret)
214 - rbuf_bump_head(buffer, ret);
215 - if (ret <= 0)
216 - {
217 - error("No response available - SSL_read()=%d", ret);
218 - goto exit_FULL;
454 + ctx->poll_fd.fd = ctx->sock;
455 +
456 + // Do the CONNECT if proxy is used
457 + if (request->proxy_host) {
458 + https_req_t req = HTTPS_REQ_T_INITIALIZER;
459 + req.request_type = HTTP_REQ_CONNECT;
460 + req.timeout_s = request->timeout_s;
461 + req.host = request->host;
462 + req.port = request->port;
463 + req.url = request->url;
464 + ctx->request = &req;
465 + if (handle_http_request(ctx)) {
466 + error("Failed to CONNECT with proxy");
467 + goto exit_sock;
468 + }
469 + if (ctx->parse_ctx.http_code != 200) {
470 + error("Proxy didn't return 200 OK (got %d)", ctx->parse_ctx.http_code);
471 + goto exit_sock;
472 }
220 - } while (!(ret = parse_http_response(buffer, &parse_ctx)));
473 + info("Proxy accepted CONNECT upgrade");
474 + }
475 + ctx->request = request;
476
222 - if (ret != PARSE_SUCCESS) {
223 - error("Error parsing HTTP response");
224 - goto exit_FULL;
477 + ctx->ssl_ctx = security_initialize_openssl_client();
478 + if (ctx->ssl_ctx==NULL) {
479 + error("Cannot allocate SSL context");
480 + goto exit_sock;
481 }
482
227 - if (parse_ctx.http_code < 200 || parse_ctx.http_code >= 300) {
228 - error("HTTP Response not Success (got %d)", parse_ctx.http_code);
229 - goto exit_FULL;
483 + ctx->ssl = SSL_new(ctx->ssl_ctx);
484 + if (ctx->ssl==NULL) {
485 + error("Cannot allocate SSL");
486 + goto exit_CTX;
487 }
488
232 - len = rbuf_pop(buffer, b, b_size);
233 - b[MIN(len, b_size-1)] = 0;
489 + SSL_set_fd(ctx->ssl, ctx->sock);
490 + ret = SSL_connect(ctx->ssl);
491 + if (ret != -1 && ret != 1) {
492 + error("SSL could not connect");
493 + goto exit_SSL;
494 + }
495 + if (ret == -1) {
496 + // expected as underlying socket is non blocking!
497 + // consult SSL_connect documentation for details
498 + int ec = SSL_get_error(ctx->ssl, ret);
499 + if (ec != SSL_ERROR_WANT_READ && ec != SSL_ERROR_WANT_WRITE) {
500 + error("Failed to start SSL connection");
501 + goto exit_SSL;
502 + }
503 + }
504 +
505 + // The actual request here
506 + if (handle_http_request(ctx)) {
507 + error("Couldn't process request");
508 + goto exit_SSL;
509 + }
510 + response->http_code = ctx->parse_ctx.http_code;
511 + if (ctx->parse_ctx.content_length > 0) {
512 + response->payload_size = ctx->parse_ctx.content_length;
513 + response->payload = mallocz(response->payload_size + 1);
514 + ret = rbuf_pop(ctx->buf_rx, response->payload, response->payload_size);
515 + if (ret != (int)response->payload_size) {
516 + error("Payload size doesn't match remaining data on the buffer!");
517 + response->payload_size = ret;
518 + }
519 + // normally we take payload as it is and copy it
520 + // but for convenience in cases where payload is sth. like
521 + // json we add terminating zero so that user of the data
522 + // doesn't have to convert to C string (0 terminated)
523 + // other uses still have correct payload_size and can copy
524 + // only exact data without affixed 0x00
525 + ((char*)response->payload)[response->payload_size] = 0; // mallocz(response->payload_size + 1);
526 + }
527 + 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);
528
529 rc = 0;
236 -exit_FULL:
530 +
531 exit_SSL:
238 - SSL_free(ssl);
532 + SSL_free(ctx->ssl);
533 exit_CTX:
240 - SSL_CTX_free(ctx);
534 + SSL_CTX_free(ctx->ssl_ctx);
535 exit_sock:
242 - close(sock);
243 -exit_buf:
244 - rbuf_free(buffer);
536 + close(ctx->sock);
537 +exit_buf_rx:
538 + rbuf_free(ctx->buf_rx);
539 +exit_req_ctx:
540 + freez(ctx);
541 return rc;
542 }
543 +
544 +void https_req_response_free(https_req_response_t *res) {
545 + freez(res->payload);
546 +}
547 +
548 +void https_req_response_init(https_req_response_t *res) {
549 + res->http_code = 0;
550 + res->payload = NULL;
551 + res->payload_size = 0;
552 +}
aclk/https_client.h
+54 -3
@@ -1,11 +1,62 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 #ifndef NETDATA_HTTPS_CLIENT_H
4 #define NETDATA_HTTPS_CLIENT_H
5
6 +#include "libnetdata/libnetdata.h"
7 +
8 typedef enum http_req_type {
5 - HTTP_REQ_GET,
6 - HTTP_REQ_POST
9 + HTTP_REQ_GET = 0,
10 + HTTP_REQ_POST,
11 + HTTP_REQ_CONNECT
12 } http_req_type_t;
13
9 -int https_request(http_req_type_t method, char *host, int port, char *url, char *b, size_t b_size, char *payload);
14 +typedef struct {
15 + http_req_type_t request_type;
16 +
17 + char *host;
18 + int port;
19 + char *url;
20 +
21 + time_t timeout_s; //timeout in seconds for the network operation (send/recv)
22 +
23 + void *payload;
24 + size_t payload_size;
25 +
26 + char *proxy_host;
27 + int proxy_port;
28 +} https_req_t;
29 +
30 +typedef struct {
31 + int http_code;
32 +
33 + void *payload;
34 + size_t payload_size;
35 +} https_req_response_t;
36 +
37 +void https_req_response_free(https_req_response_t *res);
38 +void https_req_response_init(https_req_response_t *res);
39 +
40 +#define HTTPS_REQ_RESPONSE_T_INITIALIZER \
41 + { \
42 + .http_code = 0, \
43 + .payload = NULL, \
44 + .payload_size = 0 \
45 + }
46 +
47 +#define HTTPS_REQ_T_INITIALIZER \
48 + { \
49 + .request_type = HTTP_REQ_GET, \
50 + .host = NULL, \
51 + .port = 443, \
52 + .url = NULL, \
53 + .timeout_s = 30, \
54 + .payload = NULL, \
55 + .payload_size = 0, \
56 + .proxy_host = NULL, \
57 + .proxy_port = 8080 \
58 + }
59 +
60 +int https_request(https_req_t *request, https_req_response_t *response);
61
62 #endif /* NETDATA_HTTPS_CLIENT_H */