9#include "../crypto/pem_utils.h"
17#include <sys/socket.h>
34static int sock_read(
void *ctx,
unsigned char *buf,
size_t len) {
36 ssize_t n = recv(*sock, (
char *)buf, (
int)len, 0);
51static int sock_write(
void *ctx,
const unsigned char *buf,
size_t len) {
53 ssize_t n = send(*sock, (
const char *)buf, (
int)len, 0);
69static char *extract_http_body(
const char *response,
size_t response_len) {
71 const char *body_start = strstr(response,
"\r\n\r\n");
73 log_error(
"No HTTP body found in response");
78 size_t body_len = response_len - (size_t)(body_start - response);
81 memcpy(body, body_start, body_len);
82 body[body_len] =
'\0';
92 if (strncmp(response,
"HTTP/1.", 7) != 0) {
97 const char *status = response + 9;
98 if (strncmp(status,
"200", 3) != 0) {
109char *
https_get(
const char *hostname,
const char *path) {
110 if (!hostname || !path) {
111 log_error(
"Invalid arguments to https_get");
115 log_info(
"HTTPS GET https://%s%s", hostname, path);
118 char *pem_data = NULL;
121 log_error(
"Failed to load system CA certificates");
130 if (num_anchors == 0) {
133 goto cleanup_anchors;
135 log_info(
"Loaded %zu trust anchors", num_anchors);
138 struct addrinfo hints, *result;
139 memset(&hints, 0,
sizeof(hints));
140 hints.ai_family = AF_INET;
141 hints.ai_socktype = SOCK_STREAM;
142 hints.ai_protocol = IPPROTO_TCP;
144 if (getaddrinfo(hostname,
"443", &hints, &result) != 0) {
145 log_error(
"Failed to resolve hostname: %s", hostname);
146 goto cleanup_anchors;
150 socket_t sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
153 freeaddrinfo(result);
154 goto cleanup_anchors;
158 if (connect(sock, result->ai_addr, (
int)result->ai_addrlen) != 0) {
159 log_error(
"Failed to connect to %s:443", hostname);
161 freeaddrinfo(result);
162 goto cleanup_anchors;
165 freeaddrinfo(result);
167 log_info(
"Connected to %s:443", hostname);
170 br_x509_minimal_context xc;
171 br_x509_minimal_init(&xc, &br_sha256_vtable, anchors.
buf, anchors.
ptr);
174 br_ssl_client_context sc;
175 br_ssl_client_init_full(&sc, &xc, anchors.
buf, anchors.
ptr);
178 unsigned char *iobuf;
179 iobuf =
SAFE_MALLOC(BR_SSL_BUFSIZE_BIDI,
unsigned char *);
180 br_ssl_engine_set_buffer(&sc.eng, iobuf, BR_SSL_BUFSIZE_BIDI, 1);
183 br_sslio_context ioc;
184 br_sslio_init(&ioc, &sc.eng, sock_read, &sock, sock_write, &sock);
187 br_ssl_client_reset(&sc, hostname, 0);
189 log_info(
"Starting TLS handshake with %s", hostname);
194 "GET %s HTTP/1.1\r\n"
196 "Connection: close\r\n"
197 "User-Agent: ascii-chat/" ASCII_CHAT_VERSION_STRING
"\r\n"
202 if (br_sslio_write_all(&ioc, request, (
size_t)request_len) != 0) {
203 log_error(
"Failed to send HTTP request");
206 goto cleanup_anchors;
209 br_sslio_flush(&ioc);
213 char *response_buf = NULL;
214 size_t response_capacity = 8192;
215 size_t response_len = 0;
216 response_buf =
SAFE_MALLOC(response_capacity,
char *);
220 if (response_len + 1024 > response_capacity) {
221 response_capacity *= 2;
222 response_buf =
SAFE_REALLOC(response_buf, response_capacity,
char *);
226 int n = br_sslio_read(&ioc, response_buf + response_len, response_capacity - response_len);
229 int err = br_ssl_engine_last_error(&sc.eng);
230 if (err != BR_ERR_OK) {
235 goto cleanup_anchors;
244 response_len += (size_t)n;
247 response_buf[response_len] =
'\0';
248 log_info(
"Received %zu bytes", response_len);
251 br_sslio_close(&ioc);
259 goto cleanup_anchors;
262 char *body = extract_http_body(response_buf, response_len);
267 for (
size_t i = 0; i < anchors.
ptr; i++) {
275 for (
size_t i = 0; i < anchors.
ptr; i++) {
⚠️‼️ Error and/or exit() when things go bad.
#define BUFFER_SIZE_LARGE
Large buffer size (1024 bytes)
#define SAFE_REALLOC(ptr, size, cast)
#define SAFE_MALLOC(size, cast)
br_x509_trust_anchor * buf
#define ANCHOR_LIST_INIT
Initializer for anchor_list.
size_t read_trust_anchors_from_memory(anchor_list *dst, const unsigned char *pem_data, size_t pem_len)
Read trust anchors from PEM-encoded data in memory.
void free_ta_contents(br_x509_trust_anchor *ta)
Free the contents of a trust anchor.
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
char * https_get(const char *hostname, const char *path)
Perform HTTPS GET request.
Simple HTTPS client for fetching public keys from GitHub/GitLab.
Cross-platform socket interface for ascii-chat.
Vector type for trust anchors.