From fc81fc0ea27e5d16794e1a261dddcf8a1b575814 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Thu, 16 Jul 2026 15:31:20 +0200 Subject: [PATCH] Fetch remote messages through libcurl --- src/agents/collector/external_transport.c | 154 ++++++++++++++++++++-- src/agents/collector/external_transport.h | 12 +- 2 files changed, 152 insertions(+), 14 deletions(-) diff --git a/src/agents/collector/external_transport.c b/src/agents/collector/external_transport.c index d27e5a3..7a2dd81 100644 --- a/src/agents/collector/external_transport.c +++ b/src/agents/collector/external_transport.c @@ -12,6 +12,7 @@ #define COLLECTOR_REMOTE_RESPONSE_MAX (1024U * 1024U) #define COLLECTOR_REMOTE_IDS_MAX 10000U +#define COLLECTOR_REMOTE_MESSAGE_MAX (100U * 1024U * 1024U) typedef struct { char *data; @@ -19,6 +20,12 @@ typedef struct { int overflow; } ResponseBuffer; +typedef struct { + FILE *file; + size_t length; + int overflow; +} MessageWriter; + static void SetError(char *error, size_t error_size, const char *message) { @@ -49,24 +56,44 @@ CollectResponse(char *data, size_t size, size_t count, void *context) return bytes; } -static int -AddId(CollectorRemoteIds *ids, const char *start, size_t length) +static char * +CopyToken(const char *start, size_t length) { - char **larger; char *copy; - if (length == 0) return 0; - if (ids->count >= COLLECTOR_REMOTE_IDS_MAX) return -1; + if (length == 0) return NULL; copy = malloc(length + 1); - if (copy == NULL) return -1; + if (copy == NULL) return NULL; memcpy(copy, start, length); copy[length] = '\0'; + return copy; +} + +static int +AddId(CollectorRemoteIds *ids, const char *uid, size_t uid_length, + const char *locator, size_t locator_length) +{ + CollectorRemoteId *larger; + char *uid_copy; + char *locator_copy; + if (uid_length == 0 || locator_length == 0) return 0; + if (ids->count >= COLLECTOR_REMOTE_IDS_MAX) return -1; + uid_copy = CopyToken(uid, uid_length); + locator_copy = CopyToken(locator, locator_length); + if (uid_copy == NULL || locator_copy == NULL) { + free(uid_copy); + free(locator_copy); + return -1; + } larger = realloc(ids->items, sizeof(*ids->items) * (ids->count + 1)); if (larger == NULL) { - free(copy); + free(uid_copy); + free(locator_copy); return -1; } ids->items = larger; - ids->items[ids->count++] = copy; + ids->items[ids->count].uid = uid_copy; + ids->items[ids->count].locator = locator_copy; + ids->count++; return 0; } @@ -77,13 +104,18 @@ ParsePop3Ids(char *response, CollectorRemoteIds *ids) char *save = NULL; for (line = strtok_r(response, "\r\n", &save); line != NULL; line = strtok_r(NULL, "\r\n", &save)) { + char *number_end; char *uid; + size_t number_length; while (isspace((unsigned char) *line)) line++; if (!isdigit((unsigned char) *line)) continue; - uid = line; - while (*uid != '\0' && !isspace((unsigned char) *uid)) uid++; + number_end = line; + while (*number_end != '\0' && !isspace((unsigned char) *number_end)) number_end++; + number_length = (size_t) (number_end - line); + uid = number_end; while (isspace((unsigned char) *uid)) uid++; - if (*uid != '\0' && AddId(ids, uid, strcspn(uid, " \t")) != 0) return -1; + if (*uid != '\0' && AddId(ids, uid, strcspn(uid, " \t"), + line, number_length) != 0) return -1; } return 0; } @@ -101,7 +133,8 @@ ParseImapIds(char *response, CollectorRemoteIds *ids) if (!isdigit((unsigned char) *token)) break; end = token; while (isdigit((unsigned char) *end)) end++; - if (AddId(ids, token, (size_t) (end - token)) != 0) return -1; + if (AddId(ids, token, (size_t) (end - token), token, + (size_t) (end - token)) != 0) return -1; token = end; } return 0; @@ -112,12 +145,107 @@ CollectorRemoteIdsFree(CollectorRemoteIds *ids) { size_t index; if (ids == NULL) return; - for (index = 0; index < ids->count; index++) free(ids->items[index]); + for (index = 0; index < ids->count; index++) { + free(ids->items[index].uid); + free(ids->items[index].locator); + } free(ids->items); ids->items = NULL; ids->count = 0; } +static size_t +WriteMessage(char *data, size_t size, size_t count, void *context) +{ + MessageWriter *writer = context; + size_t bytes; + if (size != 0 && count > SIZE_MAX / size) return 0; + bytes = size * count; + if (bytes > COLLECTOR_REMOTE_MESSAGE_MAX - writer->length) { + writer->overflow = 1; + return 0; + } + if (bytes != 0 && fwrite(data, 1, bytes, writer->file) != bytes) return 0; + writer->length += bytes; + return bytes; +} + +int +CollectorRemoteFetch(const CollectorExternalAccount *account, + const char *secret, const CollectorRemoteId *id, + FILE *message, size_t *message_size, + char *error, size_t error_size) +{ + CURL *curl; + CURLcode code; + MessageWriter writer = {0}; + char curl_error[CURL_ERROR_SIZE] = {0}; + char url[896]; + char *folder = NULL; + char *locator = NULL; + const char *scheme; + int result = -1; + + if (account == NULL || secret == NULL || id == NULL || id->locator == NULL || + message == NULL || message_size == NULL || + strpbrk(account->inbound_host, "/?#@\r\n") != NULL) { + SetError(error, error_size, "invalid external message fetch parameters"); + return -1; + } + *message_size = 0; + curl = curl_easy_init(); + if (curl == NULL) return -1; + locator = curl_easy_escape(curl, id->locator, 0); + if (locator == NULL) goto done; + if (strcmp(account->inbound_protocol, "imap") == 0) { + scheme = strcmp(account->inbound_tls, "implicit") == 0 ? "imaps" : "imap"; + folder = curl_easy_escape(curl, + account->destination_folder[0] != '\0' ? account->destination_folder : "INBOX", 0); + if (folder == NULL || snprintf(url, sizeof(url), "%s://%s:%d/%s;UID=%s", + scheme, account->inbound_host, account->inbound_port, folder, locator) >= (int) sizeof(url)) goto done; + } else if (strcmp(account->inbound_protocol, "pop3") == 0) { + scheme = strcmp(account->inbound_tls, "implicit") == 0 ? "pop3s" : "pop3"; + if (snprintf(url, sizeof(url), "%s://%s:%d/%s", scheme, + account->inbound_host, account->inbound_port, locator) >= (int) sizeof(url)) goto done; + } else { + SetError(error, error_size, "unsupported external account protocol"); + goto done; + } + writer.file = message; + rewind(message); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_USERNAME, account->inbound_username); + curl_easy_setopt(curl, CURLOPT_PASSWORD, secret); + curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 300L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMessage); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writer); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_error); + code = curl_easy_perform(curl); + if (code != CURLE_OK) { + SetError(error, error_size, writer.overflow ? "external message exceeds 100 MiB" : + (curl_error[0] != '\0' ? curl_error : curl_easy_strerror(code))); + goto done; + } + if (fflush(message) != 0 || writer.length == 0) { + SetError(error, error_size, "external server returned an empty message"); + goto done; + } + *message_size = writer.length; + SetError(error, error_size, ""); + result = 0; + +done: + if (folder != NULL) curl_free(folder); + if (locator != NULL) curl_free(locator); + curl_easy_cleanup(curl); + return result; +} + int CollectorRemoteList(const CollectorExternalAccount *account, const char *secret, CollectorRemoteIds *ids, diff --git a/src/agents/collector/external_transport.h b/src/agents/collector/external_transport.h index 04d0272..4b9384d 100644 --- a/src/agents/collector/external_transport.h +++ b/src/agents/collector/external_transport.h @@ -2,17 +2,27 @@ #define BONGO_COLLECTOR_EXTERNAL_TRANSPORT_H #include +#include #include "external_accounts.h" typedef struct { - char **items; + char *uid; + char *locator; +} CollectorRemoteId; + +typedef struct { + CollectorRemoteId *items; size_t count; } CollectorRemoteIds; int CollectorRemoteList(const CollectorExternalAccount *account, const char *secret, CollectorRemoteIds *ids, char *error, size_t error_size); +int CollectorRemoteFetch(const CollectorExternalAccount *account, + const char *secret, const CollectorRemoteId *id, + FILE *message, size_t *message_size, + char *error, size_t error_size); void CollectorRemoteIdsFree(CollectorRemoteIds *ids); #endif