Patch Release with Multi-Currency Wallet Detection, MIME Email Alerts and a lower_utf8 Heap Overflow Fix
lower_utf8 fixed: The output buffer was sized to the input length, but U+023A (Ⱥ) and U+023E (Ⱦ) grow from 2 to 3 bytes when lowercased. ICU's U8_APPEND does not check the capacity for ASCII characters, so once the buffer was full, every ASCII byte after that point was written past its end. lower_utf8 is applied to attachment file extensions by lua_stat and by the lower_utf8 selector transform, so the input comes from the scanned messagerules/bitcoin.lua has been replaced by rules/crypto.lua, which is built on a new lua_crypto_addresses library:BITCOIN_ADDR, LITECOIN_ADDR, DOGECOIN_ADDR, TRON_ADDR, XRP_ADDR, ZCASH_ADDR, CARDANO_ADDR, COSMOS_ADDR, STELLAR_ADDR, TON_ADDR, ETHEREUM_ADDR_MAYBE and MONERO_ADDR_MAYBE. CRYPTO_ADDR_CHECK carries the combined options.CRYPTO_ADDR_CHECK. Settings that disable a virtual symbol also disable its parent, so with virtual children, quieting ETHEREUM_ADDR_MAYBE silently turned off BITCOIN_ADDR and scam detection, and enabling one currency emitted all of them. Each symbol now stands on its own. The message is still scanned only once per task, and all symbols and the selector share the result.LEAKED_PASSWORD_SCAM_RE and the LEAKED_PASSWORD_SCAM composite now fire for any checksum-verified currency, not only Bitcoin (#6294)lua_crypto_addresses.limits:crypto_addresses() selector: Returns the wallet addresses found in a message, ready to be matched against a map or looked up in DNS. An optional first argument restricts the result to one currency. Passing typed as the second argument returns currency:address values. The selector computes the addresses itself, so it does not depend on CRYPTO_ADDR_CHECK (#6294)custom_variables block defines Lua functions that are evaluated for each message, like custom_select and custom_format. They can be referenced as $name or ${expr} in mail_from, mail_to, helo, channel, stream_key and the email_template. An ${expr} that matches no variable is evaluated as a selector expression. Only these routing fields are expanded, never connection, credential, timeout or boolean options. Message data can therefore decide who receives an alert, but not where a rule connects or how it authenticates. The expansion is also guarded against injection:Bcc;${...} is never compiled as a selector.email_parts: email_alert can now build a multipart message, so email_template no longer needs a hand-written boundary.email_parts entry takes either literal or templated content, or a raw content_from_variables body for binary data. It also takes a content_type and optional filename, disposition and encoding. email_parts_type selects the multipart subtype.auto_grouping (on by default): an inline plain text part and an inline HTML part are wrapped together in multipart/alternative, with the plain part first. If there are no other parts, the alternative becomes the whole message. More than one candidate of either kind is a configuration error. Set auto_grouping = false to keep a flat layout (#6292)email_alert are folded and RFC 2047 encoded where needed, so non-ASCII text in the template or in expanded values produces valid MIME. In address headers, only display names, group names and comments are encoded, and addresses are kept byte for byte. If a header would still carry 8-bit data, for example a non-ASCII address, the alert is stopped rather than sent with invalid headers. Headers that need no encoding are kept exactly as written. The email_auto_encode_headers option controls this and is on by default (#6292)email_parts entries are checked against a schema when the configuration loads. Invalid rules are reported as configuration errors, so rspamadm configtest fails instead of the rule breaking at runtime. Examples are unknown keys, both or neither of content and content_from_variables, bad custom callbacks, and a literal mail_from that does not parse. Rules for custom_push backends may still carry keys of their own (#6292)lua_smtp, which sends metadata_exporter's email alerts, now sends EHLO first and falls back to HELO if EHLO is rejected. It reads multi-line replies to the end before deciding. When the server advertises 8BITMIME, MAIL FROM carries BODY=8BITMIME. The keyword is matched exactly, so X8BITMIME does not count. If 8BITMIME is not advertised, RFC 6152 requires the client to convert the message to 7-bit, and that conversion now happens before MAIL FROM:message/* entities are converted recursively., or ; even when no whitespace follows, and it replaces the whitespace at the fold with a tab. Both change the text of a Subject or other free-text header once it is unfolded. The new rspamd_header_value_fold_unstructured, available in Lua as util.fold_header_unstructured(name, value[, how]), inserts line breaks only before existing whitespace. Unfolding therefore gives back the original value byte for byte. Existing callers are unchanged (#6293)smtp newline normalisation mode: normalize_newlines("crlf") turns a bare LF into CRLF but keeps a bare CR. SMTP allows CR and LF only as a CRLF pair. A bare CR left in DATA lets <CR>.<CR><LF> slip past dot-stuffing on relays that treat it as the end of data. The new smtp mode turns every bare CR and every bare LF into CRLF (#6292)rspamd_http_context_push_keepalive() declined to pool a connection, for example because the peer did not send Connection: keep-alive, it marked the connection as finished. The finish handlers then reset the connection unconditionally, which cleared that flag again. In addition, the socket read error branch called the error handler without checking the flag. Together, these let a connection whose callback context had already been freed receive a late error callback. The worker then crashed with SIGSEGV when a keepalive-enabled Lua HTTP client talked to a peer that closes the connection, such as an HTTP/1.0 server (#6288)overflow:hidden, it made all cell text invisible, so legitimate messages scored HIDDEN_TEXT and MANY_INVISIBLE_PARTS. Tables and their parts no longer inherit width and height from their parent, either during propagation or when sizes are resolved after a stylesheet merge. An explicit zero size on a cell still counts as clipping (#6287)url:get_visible() returned stale trailing bytes: https://pay<ZWSP>pal.com became https://paypal.comcom. This broke the phishing check for redirected linkshttp://exa<WJ>mple.com gave the host exa) or dropped it from hrefs. Default-ignorable code points are now accepted in hostnames and stripped before nameprep, and the URL is flagged with zw_spaces. Bidi controls are the exception, because they delimit URLs in right-to-left text. Other edge cases:html_entity hit was such a domain. The symbol also fired once per quoted copy, so a single list thread stacked it far above any other evidence. The rule is now anchored to the matched URL:h t t p s : / / are handled.ETHEREUM_ADDR_MAYBE accepts any 0x followed by 40 hex characters. A SHA-1 hash in a technical post or a quoted constant therefore opened the LEAKED_PASSWORD_SCAM composite (7.0). The other side of the composite is satisfied by a List-Unsubscribe header or the word "password", so ordinary list mail went from 3.19 to 10.19. Now only checksum-verified currencies feed LEAKED_PASSWORD_SCAM_RE and the composite. A List-Unsubscribe header alone still gates Bitcoin, as it always did, but not the newly added currencies. The word "wallet" is no longer a scam signal for any currency, Bitcoin included, because every deposit, withdrawal or payment notice that names an address also says "wallet". A withdrawal confirmation to a Tron or Bitcoin address used to score 9.20. "password" and the sextortion phrases remain signals (#6294)rspamd_mime_header_encode produced output that decoders misread:Pr=?UTF-8?Q?=C3=BCfung?= decodes as "Pr üfung".. was sent unstuffed. The receiving MTA took it as the end of DATA, so any message containing such a line, for example a diff or a text divider, was silently truncated. Lines starting with . are now stuffed, including across chunk boundaries. A bare CR now becomes CRLF, so <CR>.<CR><LF> cannot slip past dot-stuffing. RFC 5321 allows a reply line that is only the code (250); such a line no longer aborts the send. A multi-line 220- greeting is now read to its last line before EHLO. Previously, every later reply was read one line behind (#6292)http_upstreams_by_url in lua_util created upstream lists without the configuration, so they had no upstream context. If the DNS lookup at startup failed, they stayed pending forever, because the deferred resolve timer only runs for upstreams owned by that context. The GPT plugin, AWS S3, the HTTP maps backend and the DKIM vault integration stayed broken until a restart. The function now takes an optional configuration argument, falling back to the global rspamd_config, and these callers pass it (#6285)callback_from_string compiles whole-chunk callbacks: The new return_chunk parameter compiles a string as it is, and the string must itself return a function, as in local x = ...; return function() ... end. Previously, every string was wrapped as a single expression. metadata_exporter's custom callbacks now compile this way, which fixes snippets with setup statements being wrapped incorrectly. Non-string input is rejected with an error instead of crashing. Syntax errors, runtime errors and non-function results are all reported as error strings (#6292)task:get_symbol() with a table of names: When the requested symbols were present, the results were stored at a relative stack index that pointed at the symbol name rather than the result table. The call then failed with "attempt to index a string value". It now uses the absolute index of the result table (#6284)parse_content_type keeps boundary and charset as strings: util.parse_content_type documents both as string fields. Both parameters also stayed in the generic attribute table, and its loop replaced the fields with a table of values, for example for an unquoted boundary (#6292)max_ttl: max_ttl was accepted but never applied, and zset scores recorded when a sender was first seen. In zset mode, the check now refreshes the score of a known sender, treats entries older than max_ttl as unknown and removes them on insertion. Bloom filters cannot expire entries, so max_ttl applies to zset mode onlyreply_sender_privacy: The obfuscation calls used settings.sender_prefix, so the reply_sender_privacy* options were ignoredx-spamd-bar and the x-virus failure branch: The x-spamd-bar routine checked the skip setting of x-rspamd-bar, so local_headers and authenticated_headers never applied to it. The failure branch of x-virus tested the clean status instead of the fail statushtml_ignore_domains at rule level: html_ignore_domains was only read inside a rule's checks block. Set directly in the rule, as html_weight can be, it was ignoredMAXLEN to Redis streams: The MAXLEN argument for redis_stream is now always formatted as an integer (#6292)flags. An action with only a score or threshold triggered "unknown element in actions section"rspamadm fuzzy_stat: The statistics arrays start at the 1.0 epoch, but the labels assumed they started at 0.6, so every row was labelled with the wrong version. The rows are now labelled v1.0+, v1.7+ and multi-flagThis patch release fixes a heap overflow in `lower_utf8` that could be reached from attachment file extensions, and a worker crash caused by late HTTP error callbacks after keepalive was declined. Cryptocurrency detection now covers twelve currencies instead of Bitcoin alone, with independent symbols per currency, a bounded scanner that also finds split addresses, and a `crypto_addresses()` selector. The scam composite only trusts checksum-verified addresses. metadata_exporter email alerts gain custom variables, multipart bodies, automatic header encoding and schema validation. The SMTP client gains EHLO, 8BITMIME with a 7-bit fallback, and correct dot-stuffing. Fixes to HTML table layout, invisible characters in URLs and URL_OBFUSCATED_TEXT remove several sources of false positives. Before upgrading, note that the MIME parser no longer includes the line break before a boundary in a part, which changes the message digest and the digests of non-base64 parts in multipart messages. Recommended upgrade for all users.