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 */