From f7baf37515d7f963042abd61e00f8a571bb694be Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Sun, 19 Jul 2026 18:25:32 +0200 Subject: [PATCH] Complete 32-bit Trixie compatibility fixes --- cmake/FindGMime.cmake | 8 ++++---- src/agents/imap/fetch.c | 20 ++++++++++++-------- src/agents/store/mail.c | 14 +++++++++++++- src/agents/store/stored.c | 4 ++++ src/libs/sieve/sieve.c | 3 +++ src/www/bongo_web/html_mail.py | 26 ++++++++++++++++++++++++-- src/www/tests/test_html_mail.py | 11 +++++++++++ 7 files changed, 71 insertions(+), 15 deletions(-) diff --git a/cmake/FindGMime.cmake b/cmake/FindGMime.cmake index d27d40e..b8e3fdc 100644 --- a/cmake/FindGMime.cmake +++ b/cmake/FindGMime.cmake @@ -20,15 +20,15 @@ # ****************************************************************************/ find_path(GMime_INCLUDE_DIR NAMES gmime/gmime.h - PATH_SUFFIXES gmime-2.6 gmime-2.4 gmime-2.2 gmime-2.0 gmime) -find_library(GMime_LIBRARY NAMES gmime-2.6 gmime-2.4 gmime-2.2 gmime-2.0 gmime) + PATH_SUFFIXES gmime-3.0 gmime-2.6 gmime-2.4 gmime-2.2 gmime-2.0 gmime) +find_library(GMime_LIBRARY NAMES gmime-3.0 gmime-2.6 gmime-2.4 gmime-2.2 gmime-2.0 gmime) if(GMime_INCLUDE_DIR AND EXISTS "${GMime_INCLUDE_DIR}/gmime/gmime-version.h") file(STRINGS "${GMime_INCLUDE_DIR}/gmime/gmime-version.h" _GMime_VERSION_LINES - REGEX "^#define GMIME_(MAJOR|MINOR|MICRO)_VERSION[ \t]+\\([0-9]+\\)") + REGEX "^#define GMIME_(MAJOR|MINOR|MICRO)_VERSION[ \t]+\\([0-9]+U?\\)") foreach(_component MAJOR MINOR MICRO) - string(REGEX MATCH "GMIME_${_component}_VERSION[ \t]+\\(([0-9]+)\\)" + string(REGEX MATCH "GMIME_${_component}_VERSION[ \t]+\\(([0-9]+)U?\\)" _GMime_VERSION_MATCH "${_GMime_VERSION_LINES}") set(_GMime_VERSION_${_component} "${CMAKE_MATCH_1}") endforeach() diff --git a/src/agents/imap/fetch.c b/src/agents/imap/fetch.c index aaad550..b94f8af 100644 --- a/src/agents/imap/fetch.c +++ b/src/agents/imap/fetch.c @@ -240,14 +240,14 @@ SendRFC822Address(ImapSession *session, char *Address, char *Answer) return(STATUS_ABORT); } -static void -MakeRFC822Header(char *Header, unsigned long *HSize) +static size_t +MakeRFC822Header(char *Header, size_t headerSize) { char *NL, *ptr, *ptr2, *start=Header; BOOL StripLine, KeepGoing=TRUE; ptr=Header; - Header[*HSize]='\0'; + Header[headerSize]='\0'; while (KeepGoing) { StripLine=FALSE; NL=strchr(ptr,0x0a); @@ -286,7 +286,7 @@ MakeRFC822Header(char *Header, unsigned long *HSize) KeepGoing=FALSE; } } - *HSize=strlen(Header); + return strlen(Header); } __inline static char * @@ -3328,16 +3328,20 @@ GetMessageHeader(ImapSession *session, FetchStruct *FetchRequest) size_t count; /* Request the header */ - if (NMAPSendCommandF(session->store.conn, "READ %" PRIx64 " 0 %lu\r\n", FetchRequest->message->guid, FetchRequest->message->headerSize) != -1) { + if (NMAPSendCommandF(session->store.conn, "READ %" PRIx64 " 0 %" PRIu64 "\r\n", FetchRequest->message->guid, FetchRequest->message->headerSize) != -1) { if ((ccode = NMAPReadPropertyValueLength(session->store.conn, "nmap.document", &count)) == 2001) { - FetchRequest->message->headerSize = (long)count; + if (count > INT_MAX) { + return STATUS_FETCH_LIMIT; + } + FetchRequest->message->headerSize = count; FetchRequest->messageDetail.header = MemMalloc(FetchRequest->message->headerSize + 1); if (FetchRequest->messageDetail.header) { if ((ccode = NMAPReadCount(session->store.conn, FetchRequest->messageDetail.header, FetchRequest->message->headerSize)) > 0) { - if ((unsigned long)ccode == FetchRequest->message->headerSize) { + if ((size_t)ccode == count) { if (NMAPReadCrLf(session->store.conn) == 2) { FetchRequest->messageDetail.header[FetchRequest->message->headerSize] = '\0'; - MakeRFC822Header(FetchRequest->messageDetail.header, &FetchRequest->message->headerSize); + FetchRequest->message->headerSize = MakeRFC822Header( + FetchRequest->messageDetail.header, count); return(0); } } diff --git a/src/agents/store/mail.c b/src/agents/store/mail.c index 1c381d8..3be8614 100644 --- a/src/agents/store/mail.c +++ b/src/agents/store/mail.c @@ -108,9 +108,17 @@ StoreProcessIncomingMail(StoreClient *client, stream = g_mime_stream_fs_new(fd); parser = g_mime_parser_new_with_stream(stream); +#if GMIME_MAJOR_VERSION >= 3 + g_mime_parser_set_format(parser, GMIME_FORMAT_MESSAGE); +#else g_mime_parser_set_scan_from (parser, FALSE); +#endif g_object_unref(stream); +#if GMIME_MAJOR_VERSION >= 3 + message = g_mime_parser_construct_message(parser, NULL); +#else message = g_mime_parser_construct_message(parser); +#endif g_object_unref(parser); if (message == NULL) { @@ -143,7 +151,7 @@ StoreProcessIncomingMail(StoreClient *client, // treat message ID specially because we want to invent one if it // doesn't already exist. - message_id = message->message_id; + message_id = g_mime_message_get_message_id(message); if (!message_id) { snprintf(generated_message_id, sizeof(generated_message_id), "%u." GUID_FMT "@%s", document->time_created, @@ -154,7 +162,11 @@ StoreProcessIncomingMail(StoreClient *client, message_id); ConnWrite(cpipe, outstr, outstr_size); +#if GMIME_MAJOR_VERSION >= 3 + header_str = g_mime_object_get_headers(GMIME_OBJECT(message), NULL); +#else header_str = g_mime_object_get_headers(GMIME_OBJECT(message)); +#endif if (header_str != NULL) { snprintf(prop, XPL_MAX_PATH, FMT_UINT64_DEC, (uint64_t)strlen(header_str)); diff --git a/src/agents/store/stored.c b/src/agents/store/stored.c index 3522ec5..851628c 100644 --- a/src/agents/store/stored.c +++ b/src/agents/store/stored.c @@ -344,7 +344,11 @@ _XplServiceMain(int argc, char *argv[]) } // initialize the mail parsing code +#if GMIME_MAJOR_VERSION >= 3 + g_mime_init(); +#else g_mime_init(0); +#endif // create lock pool StoreInitializeFairLocks(); diff --git a/src/libs/sieve/sieve.c b/src/libs/sieve/sieve.c index e84b6cf..e24ae84 100644 --- a/src/libs/sieve/sieve.c +++ b/src/libs/sieve/sieve.c @@ -367,6 +367,8 @@ BongoSieveEvaluate(const char *script, size_t script_length, mu_static_memory_stream_create(&stream, message, message_length) != 0 || mu_message_create(&mail, NULL) != 0 || mu_message_set_stream(mail, stream, NULL) != 0) goto finish; + /* mu_message_set_stream() transfers ownership of this reference. */ + stream = NULL; mu_sieve_set_data(machine, &context); if (!install_bongo_actions(machine) || mu_sieve_compile_text(machine, normalized, normalized_length, &origin) != 0) goto finish; @@ -396,6 +398,7 @@ finish: mu_message_unref(mail); mail = NULL; } + /* Only failures before mu_message_set_stream() leave stream owned here. */ if (stream) mu_stream_unref(stream); if (machine) mu_sieve_machine_destroy(&machine); free(normalized); diff --git a/src/www/bongo_web/html_mail.py b/src/www/bongo_web/html_mail.py index 1d267f4..74ca428 100644 --- a/src/www/bongo_web/html_mail.py +++ b/src/www/bongo_web/html_mail.py @@ -50,6 +50,27 @@ DATA_IMAGE = re.compile( r"^data:image/(?:png|jpeg|gif|webp);base64,[A-Za-z0-9+/=\r\n]+$", re.IGNORECASE) MAX_DATA_IMAGE_URL = 350000 +SAFE_STYLE_VALUE = re.compile(r"^[A-Za-z0-9 #(),.%+/'\"_-]+$") + + +def _sanitize_style(value): + """Keep a conservative subset of inline CSS on HTML mail.""" + declarations = [] + for declaration in value.split(";"): + if not declaration.strip() or ":" not in declaration: + continue + name, css_value = declaration.split(":", 1) + name = name.strip().casefold() + css_value = css_value.strip() + if name not in ALLOWED_STYLE_PROPERTIES: + continue + if not css_value or not SAFE_STYLE_VALUE.fullmatch(css_value): + continue + lowered = css_value.casefold() + if "url(" in lowered or "expression(" in lowered: + continue + declarations.append("{}:{}".format(name, css_value)) + return ";".join(declarations) or None def sanitize_html(html, cleaner=None): @@ -59,6 +80,8 @@ def sanitize_html(html, cleaner=None): remote_images = [] def filter_attribute(tag, attribute, value): + if attribute == "style": + return _sanitize_style(value) if attribute == "src" and tag == "img": scheme = urlsplit(value).scheme.casefold() if scheme in ("http", "https"): @@ -87,6 +110,5 @@ def sanitize_html(html, cleaner=None): "embed", "form", "svg", "math", "meta", "link"}, attributes=ALLOWED_ATTRIBUTES, attribute_filter=filter_attribute, link_rel="noopener noreferrer nofollow", - url_schemes={"http", "https", "mailto", "cid", "data", "bongo-remote"}, - filter_style_properties=ALLOWED_STYLE_PROPERTIES) + url_schemes={"http", "https", "mailto", "cid", "data", "bongo-remote"}) return {"html": sanitized, "remote_images": remote_images} diff --git a/src/www/tests/test_html_mail.py b/src/www/tests/test_html_mail.py index 5ee3656..782ed0a 100644 --- a/src/www/tests/test_html_mail.py +++ b/src/www/tests/test_html_mail.py @@ -60,11 +60,22 @@ class HtmlMailTests(unittest.TestCase): self.assertNotIn("alert", output) self.assertNotIn("position", output) self.assertNotIn("background-image", output) + self.assertIn('style="color:red"', output) self.assertIn('src="cid:logo"', output) self.assertIn('src="bongo-remote:0"', output) self.assertEqual(result["remote_images"], ["https://tracker.example/pixel"]) + def test_style_filter_rejects_obfuscated_and_active_values(self): + result = sanitize_html( + '

safe

') + output = result["html"] + self.assertNotIn("expression", output) + self.assertNotIn("url", output) + self.assertIn("font-weight:bold", output) + if __name__ == "__main__": unittest.main()