10
11
aclk_env_t *aclk_env = NULL;
12
13
+void aclk_sensitive_memzero(void *ptr, size_t len)
14
+{
15
+ if (!ptr || len == 0)
16
+ return;
17
+
18
+ volatile unsigned char *p = (volatile unsigned char *)ptr;
19
+ while (len--)
20
+ *p++ = 0;
21
+}
22
+
23
+void aclk_sensitive_free(char **ptr)
24
+{
25
+ if (!ptr || !*ptr)
26
+ return;
27
+
28
+ size_t len = strlen(*ptr);
29
+ aclk_sensitive_memzero(*ptr, len);
30
+ freez(*ptr);
31
+ *ptr = NULL;
32
+}
33
+
34
aclk_encoding_type_t aclk_encoding_type_t_from_str(const char *str) {
35
if (!strcmp(str, "json")) {
36
return ACLK_ENC_JSON;
407
}
408
409
#define HTTP_PROXY_PREFIX "http://"
410
+#define SOCKS5_PROXY_PREFIX "socks5://"
411
+#define SOCKS5H_PROXY_PREFIX "socks5h://"
412
void aclk_set_proxy(char **ohost, int *port, char **uname, char **pwd,
413
char **log_proxy, enum mqtt_wss_proxy_type *type)
414
{
416
const char *ptr = aclk_get_proxy(&pt, false);
417
*log_proxy = (char *) aclk_get_proxy(&pt, true);
418
char *tmp;
419
+ const char *prefix = NULL;
420
+ int default_port = 0;
421
397
- if (pt != PROXY_TYPE_HTTP)
422
+ if (pt != PROXY_TYPE_HTTP && pt != PROXY_TYPE_SOCKS5 && pt != PROXY_TYPE_SOCKS5H)
423
return;
424
425
*uname = NULL;
429
char *proxy = strdupz(ptr);
430
ptr = proxy;
431
407
- if (!strncmp(ptr, HTTP_PROXY_PREFIX, strlen(HTTP_PROXY_PREFIX)))
408
- ptr += strlen(HTTP_PROXY_PREFIX);
432
+ switch (pt) {
433
+ case PROXY_TYPE_HTTP:
434
+ prefix = HTTP_PROXY_PREFIX;
435
+ default_port = 8080;
436
+ if (type)
437
+ *type = MQTT_WSS_PROXY_HTTP;
438
+ break;
439
+ case PROXY_TYPE_SOCKS5:
440
+ prefix = SOCKS5_PROXY_PREFIX;
441
+ default_port = 1080;
442
+ if (type)
443
+ *type = MQTT_WSS_PROXY_SOCKS5;
444
+ break;
445
+ case PROXY_TYPE_SOCKS5H:
446
+ prefix = SOCKS5H_PROXY_PREFIX;
447
+ default_port = 1080;
448
+ if (type)
449
+ *type = MQTT_WSS_PROXY_SOCKS5H;
450
+ break;
451
+ default:
452
+ aclk_sensitive_free(&proxy);
453
+ return;
454
+ }
455
+
456
+ if (!strncmp(ptr, prefix, strlen(prefix)))
457
+ ptr += strlen(prefix);
458
459
if ((tmp = strchr(ptr, '@'))) {
460
*tmp = 0;
465
}
466
467
if (!*ptr) {
419
- freez(proxy);
420
- freez(*uname);
421
- freez(*pwd);
468
+ aclk_sensitive_free(&proxy);
469
+ aclk_sensitive_free(uname);
470
+ aclk_sensitive_free(pwd);
471
return;
472
}
473
480
*ohost = strdupz(ptr);
481
482
if (*port <= 0 || *port > 65535)
434
- *port = 8080;
483
+ *port = default_port;
484
+
485
+ if (!type) {
486
+ aclk_sensitive_free(uname);
487
+ aclk_sensitive_free(pwd);
488
+ }
489
+
490
+ aclk_sensitive_free(&proxy);
491
+}
492
+
493
+enum mqtt_wss_proxy_type aclk_proxy_type_from_scheme(const char *proxy_url)
494
+{
495
+ if (!proxy_url || !*proxy_url)
496
+ return MQTT_WSS_DIRECT;
497
+
498
+ if (!strncmp(proxy_url, SOCKS5H_PROXY_PREFIX, strlen(SOCKS5H_PROXY_PREFIX)))
499
+ return MQTT_WSS_PROXY_SOCKS5H;
500
+
501
+ if (!strncmp(proxy_url, SOCKS5_PROXY_PREFIX, strlen(SOCKS5_PROXY_PREFIX)))
502
+ return MQTT_WSS_PROXY_SOCKS5;
503
+
504
+ if (!strncmp(proxy_url, HTTP_PROXY_PREFIX, strlen(HTTP_PROXY_PREFIX)))
505
+ return MQTT_WSS_PROXY_HTTP;
506
+
507
+ return MQTT_WSS_DIRECT;
508
+}
509
+
510
+const char *aclk_mqtt_proxy_type_to_scheme(enum mqtt_wss_proxy_type type)
511
+{
512
+ switch (type) {
513
+ case MQTT_WSS_PROXY_HTTP:
514
+ return HTTP_PROXY_PREFIX;
515
+ case MQTT_WSS_PROXY_SOCKS5:
516
+ return SOCKS5_PROXY_PREFIX;
517
+ case MQTT_WSS_PROXY_SOCKS5H:
518
+ return SOCKS5H_PROXY_PREFIX;
519
+ default:
520
+ return "";
521
+ }
522
+}
523
+
524
+static int aclk_poll_for_io(int fd, short events, int timeout_ms)
525
+{
526
+ struct pollfd pfd = {
527
+ .fd = fd,
528
+ .events = events,
529
+ .revents = 0
530
+ };
531
+
532
+ int rc;
533
+ do {
534
+ rc = poll(&pfd, 1, timeout_ms);
535
+ } while (rc < 0 && errno == EINTR);
536
+
537
+ if (rc <= 0)
538
+ return rc;
539
+
540
+ if ((pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) != 0)
541
+ return -1;
542
+
543
+ return 1;
544
+}
545
+
546
+static int aclk_timeout_remaining_ms(usec_t start, int timeout_ms)
547
+{
548
+ if (timeout_ms <= 0)
549
+ return 0;
550
+
551
+ usec_t elapsed_ms = (now_monotonic_usec() - start) / USEC_PER_MS;
552
+ if (elapsed_ms >= (usec_t)timeout_ms)
553
+ return 0;
554
+
555
+ return timeout_ms - (int)elapsed_ms;
556
+}
557
+
558
+static int aclk_write_all_timeout(int fd, const void *buf, size_t len, int timeout_ms)
559
+{
560
+ size_t written = 0;
561
+ usec_t start = now_monotonic_usec();
562
+
563
+ while (written < len) {
564
+ int remaining_ms = aclk_timeout_remaining_ms(start, timeout_ms);
565
+ if (remaining_ms <= 0)
566
+ return 1;
567
+
568
+ int rc = aclk_poll_for_io(fd, POLLOUT, remaining_ms);
569
+ if (rc <= 0)
570
+ return 1;
571
+
572
+ ssize_t n = write(fd, ((const uint8_t *)buf) + written, len - written);
573
+ if (n < 0) {
574
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
575
+ if ((now_monotonic_usec() - start) / USEC_PER_MS > (usec_t)timeout_ms)
576
+ return 1;
577
+ continue;
578
+ }
579
+ return 1;
580
+ }
581
+ written += (size_t)n;
582
+ }
583
+
584
+ return 0;
585
+}
586
+
587
+static int aclk_read_exact_timeout(int fd, void *buf, size_t len, int timeout_ms)
588
+{
589
+ size_t got = 0;
590
+ usec_t start = now_monotonic_usec();
591
+
592
+ while (got < len) {
593
+ int remaining_ms = aclk_timeout_remaining_ms(start, timeout_ms);
594
+ if (remaining_ms <= 0)
595
+ return 1;
596
+
597
+ int rc = aclk_poll_for_io(fd, POLLIN, remaining_ms);
598
+ if (rc <= 0)
599
+ return 1;
600
+
601
+ ssize_t n = read(fd, ((uint8_t *)buf) + got, len - got);
602
+ if (n == 0)
603
+ return 1;
604
+ if (n < 0) {
605
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
606
+ if ((now_monotonic_usec() - start) / USEC_PER_MS > (usec_t)timeout_ms)
607
+ return 1;
608
+ continue;
609
+ }
610
+ return 1;
611
+ }
612
+ got += (size_t)n;
613
+ }
614
+
615
+ return 0;
616
+}
617
+
618
+static int aclk_http_proxy_negotiate(int sockfd, const char *proxy_username, const char *proxy_password,
619
+ const char *target_host, int target_port, int timeout_ms)
620
+{
621
+ int result = 1;
622
+ bool has_creds = (proxy_username && *proxy_username);
623
+ usec_t start = now_monotonic_usec();
624
+ char req[4096];
625
+ size_t off = 0;
626
+ int rc = snprintf(req + off, sizeof(req) - off, "CONNECT %s:%d HTTP/1.1\r\nHost: %s\r\n",
627
+ target_host, target_port, target_host);
628
+ if (rc < 0 || (size_t)rc >= sizeof(req) - off)
629
+ goto cleanup;
630
+ off += (size_t)rc;
631
+
632
+ if (has_creds) {
633
+ size_t pass_len = proxy_password ? strlen(proxy_password) : 0;
634
+ size_t creds_plain_len = strlen(proxy_username) + pass_len + 1;
635
+ char *creds_plain = callocz(1, creds_plain_len + 1);
636
+ snprintfz(creds_plain, creds_plain_len + 1, "%s:%s", proxy_username, proxy_password ? proxy_password : "");
637
+
638
+ int creds_base64_len = (((4 * (int)creds_plain_len / 3) + 3) & ~3);
639
+ creds_base64_len += (1 + (creds_base64_len / 64)) * (int)strlen("\n");
640
+ char *creds_base64 = callocz(1, (size_t)creds_base64_len + 1);
641
+ (void)netdata_base64_encode((unsigned char *)creds_base64, (unsigned char *)creds_plain, creds_plain_len);
642
+
643
+ rc = snprintf(req + off, sizeof(req) - off, "Proxy-Authorization: Basic %s\r\n", creds_base64);
644
+ aclk_sensitive_free(&creds_plain);
645
+ aclk_sensitive_free(&creds_base64);
646
+ if (rc < 0 || (size_t)rc >= sizeof(req) - off)
647
+ goto cleanup;
648
+ off += (size_t)rc;
649
+ }
650
+
651
+ if (off + 2 >= sizeof(req))
652
+ goto cleanup;
653
+ req[off++] = '\r';
654
+ req[off++] = '\n';
655
+
656
+ if (aclk_write_all_timeout(sockfd, req, off, aclk_timeout_remaining_ms(start, timeout_ms)))
657
+ goto cleanup;
658
+
659
+ // Read the HTTP response one byte at a time to avoid over-reading
660
+ // beyond the header terminator into TLS handshake data.
661
+ char resp[4096];
662
+ size_t used = 0;
663
+ while (used < sizeof(resp) - 1) {
664
+ int remaining_ms = aclk_timeout_remaining_ms(start, timeout_ms);
665
+ if (remaining_ms <= 0)
666
+ goto cleanup;
667
+
668
+ int prc = aclk_poll_for_io(sockfd, POLLIN, remaining_ms);
669
+ if (prc <= 0)
670
+ goto cleanup;
671
+
672
+ ssize_t n = read(sockfd, resp + used, 1);
673
+ if (n == 0)
674
+ goto cleanup;
675
+ if (n < 0) {
676
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
677
+ continue;
678
+ goto cleanup;
679
+ }
680
+ used += (size_t)n;
681
+ resp[used] = '\0';
682
+
683
+ // Check if we have received the complete header terminator
684
+ if (used >= 4 && memcmp(resp + used - 4, "\r\n\r\n", 4) == 0)
685
+ break;
686
+ }
687
+
688
+ if (used < 4 || memcmp(resp + used - 4, "\r\n\r\n", 4) != 0)
689
+ goto cleanup;
690
+
691
+ if (strncmp(resp, "HTTP/1.1 ", 9) != 0 && strncmp(resp, "HTTP/1.0 ", 9) != 0)
692
+ goto cleanup;
693
+
694
+ if (!isdigit((unsigned char)resp[9]) || !isdigit((unsigned char)resp[10]) || !isdigit((unsigned char)resp[11])) {
695
+ netdata_log_error("ACLK: HTTP proxy response missing valid status code");
696
+ goto cleanup;
697
+ }
698
+
699
+ int status = atoi(resp + 9);
700
+ if (status == 200) {
701
+ result = 0;
702
+ } else {
703
+ // extract the status line (first line) for logging
704
+ char *eol = strstr(resp, "\r\n");
705
+ if (eol)
706
+ *eol = '\0';
707
+ netdata_log_error("ACLK: HTTP proxy CONNECT to %s:%d failed with status %d: %s",
708
+ target_host, target_port, status, resp);
709
+ result = 1;
710
+ }
711
+
712
+cleanup:
713
+ if (has_creds)
714
+ aclk_sensitive_memzero(req, sizeof(req));
715
+ return result;
716
+}
717
+
718
+static int aclk_socks5_resolve_local(const char *host, uint8_t *atype, uint8_t *addr, size_t *addr_len)
719
+{
720
+ struct in_addr ipv4;
721
+ struct in6_addr ipv6;
722
+ if (inet_pton(AF_INET, host, &ipv4) == 1) {
723
+ *atype = 0x01;
724
+ memcpy(addr, &ipv4, sizeof(ipv4));
725
+ *addr_len = sizeof(ipv4);
726
+ return 0;
727
+ }
728
+ if (inet_pton(AF_INET6, host, &ipv6) == 1) {
729
+ *atype = 0x04;
730
+ memcpy(addr, &ipv6, sizeof(ipv6));
731
+ *addr_len = sizeof(ipv6);
732
+ return 0;
733
+ }
734
+
735
+ struct addrinfo hints = {
736
+ .ai_family = AF_UNSPEC,
737
+ .ai_socktype = SOCK_STREAM,
738
+ .ai_flags = AI_ADDRCONFIG
739
+ };
740
+ struct addrinfo *res = NULL;
741
+ if (getaddrinfo(host, NULL, &hints, &res) != 0 || !res)
742
+ return 1;
743
+
744
+ int rc = 1;
745
+
746
+ // Prefer IPv4 for compatibility with SOCKS proxies that don't accept ATYP=0x04 (IPv6).
747
+ for (struct addrinfo *it = res; it; it = it->ai_next) {
748
+ if (it->ai_family == AF_INET && it->ai_addrlen >= sizeof(struct sockaddr_in)) {
749
+ struct sockaddr_in *sa = (struct sockaddr_in *)it->ai_addr;
750
+ *atype = 0x01;
751
+ memcpy(addr, &sa->sin_addr, sizeof(sa->sin_addr));
752
+ *addr_len = sizeof(sa->sin_addr);
753
+ rc = 0;
754
+ break;
755
+ }
756
+ }
757
+
758
+ if (rc != 0) {
759
+ for (struct addrinfo *it = res; it; it = it->ai_next) {
760
+ if (it->ai_family == AF_INET6 && it->ai_addrlen >= sizeof(struct sockaddr_in6)) {
761
+ struct sockaddr_in6 *sa = (struct sockaddr_in6 *)it->ai_addr;
762
+ *atype = 0x04;
763
+ memcpy(addr, &sa->sin6_addr, sizeof(sa->sin6_addr));
764
+ *addr_len = sizeof(sa->sin6_addr);
765
+ rc = 0;
766
+ break;
767
+ }
768
+ }
769
+ }
770
+
771
+ freeaddrinfo(res);
772
+ return rc;
773
+}
774
+
775
+static int aclk_socks5_proxy_negotiate(int sockfd, enum mqtt_wss_proxy_type proxy_type,
776
+ const char *proxy_username, const char *proxy_password,
777
+ const char *target_host, int target_port, int timeout_ms)
778
+{
779
+ usec_t start = now_monotonic_usec();
780
+
781
+ uint8_t greeting[4] = { 0x05, 0x01, 0x00, 0x00 };
782
+ size_t greeting_len = 3;
783
+ if (proxy_username && *proxy_username) {
784
+ greeting[1] = 0x02;
785
+ greeting[2] = 0x00;
786
+ greeting[3] = 0x02;
787
+ greeting_len = 4;
788
+ }
789
+
790
+ if (aclk_write_all_timeout(sockfd, greeting, greeting_len, aclk_timeout_remaining_ms(start, timeout_ms))) {
791
+ return 1;
792
+ }
793
+
794
+ uint8_t greeting_reply[2];
795
+ if (aclk_read_exact_timeout(sockfd, greeting_reply, sizeof(greeting_reply), aclk_timeout_remaining_ms(start, timeout_ms))) {
796
+ return 1;
797
+ }
798
+ if (greeting_reply[0] != 0x05 || greeting_reply[1] == 0xFF) {
799
+ netdata_log_error("ACLK: SOCKS5 proxy rejected methods (ver=0x%02x method=0x%02x)",
800
+ greeting_reply[0], greeting_reply[1]);
801
+ return 1;
802
+ }
803
+
804
+ if (greeting_reply[1] == 0x02) {
805
+ if (!proxy_username || !*proxy_username) {
806
+ netdata_log_error("ACLK: SOCKS5 proxy requires username/password auth but credentials are missing");
807
+ return 1;
808
+ }
809
+ size_t user_len = strlen(proxy_username);
810
+ size_t pass_len = proxy_password ? strlen(proxy_password) : 0;
811
+ if (user_len > UINT8_MAX || pass_len > UINT8_MAX) {
812
+ netdata_log_error("ACLK: SOCKS5 credentials exceed protocol limits");
813
+ return 1;
814
+ }
815
+
816
+ uint8_t auth_req[513];
817
+ size_t pos = 0;
818
+ auth_req[pos++] = 0x01;
819
+ auth_req[pos++] = (uint8_t)user_len;
820
+ memcpy(auth_req + pos, proxy_username, user_len);
821
+ pos += user_len;
822
+ auth_req[pos++] = (uint8_t)pass_len;
823
+ if (pass_len) {
824
+ memcpy(auth_req + pos, proxy_password, pass_len);
825
+ pos += pass_len;
826
+ }
827
+
828
+ int auth_rc = aclk_write_all_timeout(sockfd, auth_req, pos, aclk_timeout_remaining_ms(start, timeout_ms));
829
+ aclk_sensitive_memzero(auth_req, sizeof(auth_req));
830
+ if (auth_rc) {
831
+ return 1;
832
+ }
833
+
834
+ uint8_t auth_reply[2];
835
+ if (aclk_read_exact_timeout(sockfd, auth_reply, sizeof(auth_reply), aclk_timeout_remaining_ms(start, timeout_ms))) {
836
+ return 1;
837
+ }
838
+ if (auth_reply[0] != 0x01 || auth_reply[1] != 0x00) {
839
+ netdata_log_error("ACLK: SOCKS5 auth failed (ver=0x%02x status=0x%02x)",
840
+ auth_reply[0], auth_reply[1]);
841
+ return 1;
842
+ }
843
+ } else if (greeting_reply[1] != 0x00) {
844
+ netdata_log_error("ACLK: SOCKS5 proxy selected unsupported method 0x%02x", greeting_reply[1]);
845
+ return 1;
846
+ }
847
+
848
+ uint8_t connect_req[300];
849
+ size_t pos = 0;
850
+ connect_req[pos++] = 0x05;
851
+ connect_req[pos++] = 0x01;
852
+ connect_req[pos++] = 0x00;
853
+
854
+ if (proxy_type == MQTT_WSS_PROXY_SOCKS5H) {
855
+ size_t host_len = strlen(target_host);
856
+ if (host_len == 0 || host_len > UINT8_MAX) {
857
+ netdata_log_error("ACLK: SOCKS5H target hostname length invalid (%zu)", host_len);
858
+ return 1;
859
+ }
860
+ // 1 (atype) + 1 (length) + host_len + 2 (port)
861
+ if (pos + 1 + 1 + host_len + 2 > sizeof(connect_req)) {
862
+ netdata_log_error("ACLK: SOCKS5H CONNECT request too large for buffer");
863
+ return 1;
864
+ }
865
+ connect_req[pos++] = 0x03;
866
+ connect_req[pos++] = (uint8_t)host_len;
867
+ memcpy(connect_req + pos, target_host, host_len);
868
+ pos += host_len;
869
+ } else {
870
+ uint8_t atyp = 0;
871
+ uint8_t addr[16];
872
+ size_t addr_len = 0;
873
+ if (aclk_socks5_resolve_local(target_host, &atyp, addr, &addr_len)) {
874
+ netdata_log_error("ACLK: SOCKS5 local DNS resolution failed for target host '%s'", target_host);
875
+ return 1;
876
+ }
877
+ // 1 (atype) + addr_len + 2 (port)
878
+ if (pos + 1 + addr_len + 2 > sizeof(connect_req)) {
879
+ netdata_log_error("ACLK: SOCKS5 CONNECT request too large for buffer");
880
+ return 1;
881
+ }
882
+ connect_req[pos++] = atyp;
883
+ memcpy(connect_req + pos, addr, addr_len);
884
+ pos += addr_len;
885
+ }
886
+
887
+ connect_req[pos++] = (uint8_t)((target_port >> 8) & 0xFF);
888
+ connect_req[pos++] = (uint8_t)(target_port & 0xFF);
889
+
890
+ if (aclk_write_all_timeout(sockfd, connect_req, pos, aclk_timeout_remaining_ms(start, timeout_ms))) {
891
+ return 1;
892
+ }
893
+
894
+ uint8_t reply_hdr[4];
895
+ if (aclk_read_exact_timeout(sockfd, reply_hdr, sizeof(reply_hdr), aclk_timeout_remaining_ms(start, timeout_ms))) {
896
+ return 1;
897
+ }
898
+ if (reply_hdr[0] != 0x05 || reply_hdr[1] != 0x00) {
899
+ netdata_log_error("ACLK: SOCKS5 CONNECT failed (ver=0x%02x rep=0x%02x atyp=0x%02x)",
900
+ reply_hdr[0], reply_hdr[1], reply_hdr[3]);
901
+ return 1;
902
+ }
903
436
- if (type)
437
- *type = MQTT_WSS_PROXY_HTTP;
438
- else {
439
- freez(*uname);
440
- freez(*pwd);
904
+ size_t to_read = 0;
905
+ switch (reply_hdr[3]) {
906
+ case 0x01:
907
+ to_read = 4 + 2;
908
+ break;
909
+ case 0x03: {
910
+ uint8_t domain_len = 0;
911
+ if (aclk_read_exact_timeout(sockfd, &domain_len, 1, aclk_timeout_remaining_ms(start, timeout_ms))) {
912
+ return 1;
913
+ }
914
+ to_read = (size_t)domain_len + 2;
915
+ break;
916
+ }
917
+ case 0x04:
918
+ to_read = 16 + 2;
919
+ break;
920
+ default:
921
+ netdata_log_error("ACLK: SOCKS5 CONNECT reply has invalid ATYP 0x%02x", reply_hdr[3]);
922
+ return 1;
923
}
924
443
- freez(proxy);
925
+ uint8_t discard[260];
926
+ if (to_read > sizeof(discard)) {
927
+ return 1;
928
+ }
929
+
930
+ if (aclk_read_exact_timeout(sockfd, discard, to_read, aclk_timeout_remaining_ms(start, timeout_ms))) {
931
+ return 1;
932
+ }
933
+
934
+ return 0;
935
+}
936
+
937
+int aclk_proxy_negotiation_connect(int sockfd, enum mqtt_wss_proxy_type proxy_type,
938
+ const char *proxy_username, const char *proxy_password,
939
+ const char *target_host, int target_port, int timeout_ms)
940
+{
941
+ if (proxy_type == MQTT_WSS_DIRECT)
942
+ return 0;
943
+
944
+ if (proxy_type == MQTT_WSS_PROXY_HTTP)
945
+ return aclk_http_proxy_negotiate(sockfd, proxy_username, proxy_password, target_host, target_port, timeout_ms);
946
+
947
+ if (proxy_type == MQTT_WSS_PROXY_SOCKS5 || proxy_type == MQTT_WSS_PROXY_SOCKS5H)
948
+ return aclk_socks5_proxy_negotiate(sockfd, proxy_type, proxy_username, proxy_password, target_host, target_port, timeout_ms);
949
+
950
+ return 1;
951
}