Fetch remote messages through libcurl

This commit is contained in:
Mario Fetka
2026-07-16 15:31:20 +02:00
parent 63c33c8f92
commit fc81fc0ea2
2 changed files with 152 additions and 14 deletions
+141 -13
View File
@@ -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,
+11 -1
View File
@@ -2,17 +2,27 @@
#define BONGO_COLLECTOR_EXTERNAL_TRANSPORT_H
#include <stddef.h>
#include <stdio.h>
#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