[pfx] cleanup(8): recipient_canonical can emit an over-long socketmap query with no operator guard; PERM then retried as 451

Dmytro Alieksieiev via Postfix-users <[email protected]> Wed, 15 Jul 2026 21:19:29 +0200
Newsgroups gmane.mail.postfix.user
Message-ID <[email protected]>
This is a multi-part message in MIME format.
--===============6417191368454346579==
Content-Type: multipart/alternative;
 boundary="------------Pua1mHggK15LbjXHqJf4F02b"
Content-Language: en-US

This is a multi-part message in MIME format.
--------------Pua1mHggK15LbjXHqJf4F02b
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit

Hi Wietse, Viktor,

I ran into a funky interaction between|recipient_canonical|header 
rewriting and a socketmap map (PostSRSd, doing reverse-SRS), where a 
single malformed/header/address can deterministically cancel an 
otherwise-deliverable message. I think I've narrowed the root cause down 
to something that postfix itself has no way to guard against, and I'd 
love your read on it and maybe propose a way to solve this issue.


    Setup

An incoming relay accepts mail for forwarding and hands it to an 
outgoing relay. The outgoing relay does reverse-SRS via a socketmap and 
also canonicalizes header recipients (so the|To:|in a DSN matches the 
real MAIL TO, per RFC 3464):

|recipient_canonical_maps = socketmap:inet:127.0.0.1:10003:reverse 
recipient_canonical_classes = envelope_recipient, header_recipient 
remote_header_rewrite_domain = domain.invalid # remote clients -> 
rewrite context = remote |

The socketmap server is PostSRSd 2.x, but — importantly — it is*not*the 
culprit; any socketmap with a bounded request buffer behaves the same 
(see below).


    Trigger

A forwarded spam message (envelope recipient perfectly valid and 
deliverable) carries a*cosmetically mangled|To:|or|Cc:|header*. The 
interesting part is not the two-bare-|@|addr-spec — that turns out to be 
harmless — but an*unbalanced double-quote combined with a long folded 
continuation*:

|Cc: [[email protected]]@host.example.org" 
<[email protected]@evil.example.net> 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
... (~2 KB of folded junk, no closing quote) ... 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA] |

The|"|after|host.example.org|is never closed. So|tok822|treats 
everything that follows — including the entire folded blob — as*one 
quoted-string localpart*, and in the remote rewrite context 
appends|@domain.invalid|, producing a single ~2 KB address:

|"<~2000 chars of junk>"@domain.invalid |


    What happens

|cleanup(8)|issues that ~2 KB string as the canonical-map (socketmap) 
query. It exceeds the socketmap server's request buffer 
(PostSRSd's|PAYLOAD_SIZE|is 512 bytes), so the server returns a 
permanent error:

|warning: socketmap:inet:127.0.0.1:10003:reverse socketmap server 
permanent error: Too big. warning: reverse lookup error for "" 
<[email protected]@evil.example.net> ...[AAAA...]"@domain.invalid" 
warning: ABCDEF: recipient_canonical_maps map lookup problem for ... -- 
message not accepted, try again later ABCDEF: removed (canceled) |

and the upstream sender gets:

|451 4.3.0 Error: queue file write error (in reply to end of DATA command) |

Because it's a 451, the upstream relay retries the message for its whole 
queue lifetime — so one attacker-controlled cosmetic header turns into a 
message that can never drain and slowly fills a relay with deferred 
retries. (A different socketmap/version returns|PERM Invalid 
query.|instead of|Too big.|for the same input; same class, same outcome.)


    Root cause, and why I can't defend against it inside postfix

The address/content/is fine — the same recipient handed to the map as a 
normal-length key returns "not found" and delivers cleanly. What breaks 
is purely that*cleanup builds a canonical-map query that is too large to 
be represented*, and then treats the resulting|PERM|as fatal. Two 
separate things line up:

 1. An*unbalanced quote*lets a folded header expand into one enormous
    address token. Postfix is arguably being lenient/correct here, but
    the result is a multi-KB "address".
 2. *A failed lookup during/optional header/canonicalization is fatal to
    the whole message.*The junk address erroring is fine —
    it/should/fail. What's disproportionate is the blast
    radius:|header_recipient|rewriting is a cosmetic, best-effort nicety
    (align a DSN|To:|for RFC 3464), yet its failure cancels a message
    whose envelope recipient is perfectly valid and deliverable. A
    cosmetic step failing shouldn't take down the delivery.
 3. *A/permanent/map error (|PERM|) is reported to the client as
    a/transient/|451|.*This is what turns a one-off into a queue-wedge:
    the socketmap said/permanent/, but the upstream sees|451 queue file
    write error|, so it retries the identical, deterministically-failing
    message for the entire queue lifetime. Had the same condition
    surfaced as a|5xx|, the message would simply bounce and the queue
    would drain — annoying, but not self-perpetuating.

And here's the part that motivated writing to you:*postfix gives an 
operator no lever to prevent its own doomed query.*

  * There's no introspection point between "cleanup extracted this
    header address" and "cleanup sends it to the map" — I can't see or
    cap the key that's about to be sent.
  * Canonical/socketmap lookups have*no key-length limit*; cleanup will
    happily emit a key it must know the socketmap can't accept.
  * |header_checks|match a*whole logical header*, not individual
    addresses, so I can't write a rule like "reject if any single
    address in To/Cc exceeds N bytes or X symbols." I can only
    pattern-match the header text as a blob, which is fragile against
    folding.
  * The failure surfaces as a generic|451 ... queue file write error|—
    indistinguishable from disk-full, a database outage, etc. — so I
    can't even intercept/this specific/condition and handle it differently.

Net: the only place I/can/currently block this is a milter/content 
filter in front of the relay (which is what I'll do), but that feels 
like papering over a postfix-side sharp edge — and it's a leaky patch, 
because a milter parses headers/very/differently from postfix's|tok822|. 
The filter's address parser doesn't reproduce the 
unclosed-quote-swallows-the-fold behavior, so what the milter sees as 
the recipient set and what|cleanup|will actually hand the socketmap are 
two different things. I can approximate (flag unbalanced quotes / 
over-long tokens on the raw header), but I can't reliably pre-empt 
postfix's own tokenization from outside it.


    Questions / possible directions

 1. Should a lookup failure during*optional header*canonicalization
    (|header_recipient|) be fatal at all? Logging and leaving the header
    untouched (continue, don't cancel) would avoid the whole class of
    problem for cosmetic rewrites.
 2. Could the canonical/socketmap client*cap the key length*it will send
    — e.g. if the rewritten address exceeds a representable/
    configurable maximum, treat it as "no match" (skip the rewrite)
    instead of emitting a query the server must reject? Postfix knows
    the key before it sends it.
 3. Failing that, is there appetite for a*per-address length
    guard*operators could set (reject or skip addresses whose serialized
    form exceeds N), given|header_checks|can't express per-address
    limits today?
 4. Would distinguishing a socketmap|PERM|on/header/canonicalization
    from a genuine transport/tempfail — with its own status rather than
    the generic|queue file write error|— be reasonable, so operators can
    act on it?
 5. Most impactful, and independent of the above: should a
    socketmap*|PERM|map to a permanent|5xx|*rather than a
    transient|451|? The server explicitly said/permanent/, yet the
    client is told to retry forever. Surfacing it as|5xx|(bounce)
    instead of|451|(retry) would, on its own, prevent the deterministic
    queue-wedge — even if everything else stayed as-is.


    Minimal reproduction

Stock postfix + any socketmap that bounds its request (e.g. PostSRSd, 
512-byte payload):

|recipient_canonical_maps = socketmap:inet:127.0.0.1:10003:reverse 
recipient_canonical_classes = envelope_recipient, header_recipient 
remote_header_rewrite_domain = domain.invalid 
local_header_rewrite_clients = <something that excludes the injecting 
client, so context = remote> |

Inject (clean envelope, junk only in the header) a message 
whose|To:|or|Cc:|opens a double-quote that is never closed and is 
followed by ~2 KB of folded content. cleanup issues an oversized 
socketmap query, gets|PERM|, and cancels the message with|451 4.3.0 
Error: queue file write error|. Balancing/closing the quote (so the 
token stays a normal-length address) makes it deliver normally — 
confirming size, not content, is the fault.

Thanks for postfix, and for reading this far.

-- 
Regards,
Dmytro Alieksieiev
DevOps Engineer

--------------Pua1mHggK15LbjXHqJf4F02b
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Hi
      Wietse, Viktor,</p>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">I
      ran into a funky interaction between<span> </span><code>recipient_canonical</code><span> </span>header
      rewriting and a socketmap map (PostSRSd, doing reverse-SRS), where
      a single malformed<span> </span><em>header</em><span> </span>address
      can deterministically cancel an otherwise-deliverable message. I
      think I've narrowed the root cause down to something that postfix
      itself has no way to guard against, and I'd love your read on it
      and maybe propose a way to solve this issue.</p>
    <h2 id="setup"
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Setup</h2>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">An
      incoming relay accepts mail for forwarding and hands it to an
      outgoing relay. The outgoing relay does reverse-SRS via a
      socketmap and also canonicalizes header recipients (so the<span> </span><code>To:</code><span> </span>in
      a DSN matches the real MAIL TO, per RFC 3464):</p>
    <pre
style="font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><code><span
    class="hljs-attr">recipient_canonical_maps</span>    = socketmap:inet:<span
    class="hljs-number">127.0</span>.<span class="hljs-number">0.1</span>:<span
    class="hljs-number">10003</span>:reverse
<span class="hljs-attr">recipient_canonical_classes</span> = envelope_recipient, header_recipient
<span class="hljs-attr">remote_header_rewrite_domain</span> = domain.invalid      # remote clients -&gt; rewrite context = remote
</code></pre>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">The
      socketmap server is PostSRSd 2.x, but — importantly — it is<span> </span><strong>not</strong><span> </span>the
      culprit; any socketmap with a bounded request buffer behaves the
      same (see below).</p>
    <h2 id="trigger"
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Trigger</h2>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">A
      forwarded spam message (envelope recipient perfectly valid and
      deliverable) carries a<span> </span><strong>cosmetically mangled<span> </span><code>To:</code><span> </span>or<span> </span><code>Cc:</code><span> </span>header</strong>.
      The interesting part is not the two-bare-<code>@</code><span> </span>addr-spec
      — that turns out to be harmless — but an<span> </span><strong>unbalanced
        double-quote combined with a long folded continuation</strong>:</p>
    <pre
style="font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><code><span
    class="hljs-selector-tag">Cc</span>: <span
    class="hljs-selector-attr">[<a class="moz-txt-link-abbreviated" href="mailto:[email protected]">[email protected]</a>]</span>@<span
    class="hljs-keyword">host</span>.<span class="hljs-keyword">example</span>.<span
    class="hljs-keyword">org</span>" <a class="moz-txt-link-rfc2396E" href="mailto:[email protected]@evil.example.net">&lt;[email protected]@evil.example.net&gt;</a>
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 ... (~<span class="hljs-number">2</span> KB of folded junk, no closing quote) ...
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]
</code></pre>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">The<span> </span><code>"</code><span> </span>after<span> </span><code>host.example.org</code><span> </span>is
      never closed. So<span> </span><code>tok822</code><span> </span>treats
      everything that follows — including the entire folded blob — as<span> </span><strong>one
        quoted-string localpart</strong>, and in the remote rewrite
      context appends<span> </span><code>@domain.invalid</code>,
      producing a single ~2 KB address:</p>
    <pre
style="font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><code><span
    class="hljs-string">"&lt;~2000 chars of junk&gt;"</span><span
    class="hljs-meta">@domain</span>.invalid
</code></pre>
    <h2 id="what-happens"
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">What
      happens</h2>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><code>cleanup(8)</code><span> </span>issues
      that ~2 KB string as the canonical-map (socketmap) query. It
      exceeds the socketmap server's request buffer (PostSRSd's<span> </span><code>PAYLOAD_SIZE</code><span> </span>is
      512 bytes), so the server returns a permanent error:</p>
    <pre
style="font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><code><span
    class="hljs-string">warning:</span> <span class="hljs-string">socketmap:</span><span
    class="hljs-string">inet:</span><span class="hljs-number">127.0</span><span
    class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span
    class="hljs-number">10003</span>:reverse socketmap server permanent <span
    class="hljs-string">error:</span> Too big.
<span class="hljs-string">warning:</span> reverse lookup error <span
    class="hljs-keyword">for</span> <span class="hljs-string">""</span> &lt;user<span
    class="hljs-meta">@example</span>.com<span class="hljs-meta">@evil</span>.example.net&gt; ...[AAAA...]<span
    class="hljs-string">"@domain.invalid"</span>
<span class="hljs-string">warning:</span> <span class="hljs-string">ABCDEF:</span> recipient_canonical_maps map lookup problem <span
    class="hljs-keyword">for</span> ... -- message not accepted, <span
    class="hljs-keyword">try</span> again later
<span class="hljs-string">ABCDEF:</span> removed (canceled)
</code></pre>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">and
      the upstream sender gets:</p>
    <pre
style="font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><code><span
    class="hljs-symbol">451 </span><span class="hljs-number">4.3.0</span> <span
    class="hljs-keyword">Error</span>: queue file <span
    class="hljs-keyword">write</span> <span class="hljs-keyword">error</span> (in reply <span
    class="hljs-keyword">to</span> <span class="hljs-keyword">end</span> of <span
    class="hljs-keyword">DATA</span> command)
</code></pre>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Because
      it's a 451, the upstream relay retries the message for its whole
      queue lifetime — so one attacker-controlled cosmetic header turns
      into a message that can never drain and slowly fills a relay with
      deferred retries. (A different socketmap/version returns<span> </span><code>PERM
        Invalid query.</code><span> </span>instead of<span> </span><code>Too
        big.</code><span> </span>for the same input; same class, same
      outcome.)</p>
    <h2 id="root-cause-and-why-i-can-t-defend-against-it-inside-postfix"
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Root
      cause, and why I can't defend against it inside postfix</h2>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">The
      address<span> </span><em>content</em><span> </span>is fine — the
      same recipient handed to the map as a normal-length key returns
      "not found" and delivers cleanly. What breaks is purely that<span> </span><strong>cleanup
        builds a canonical-map query that is too large to be represented</strong>,
      and then treats the resulting<span> </span><code>PERM</code><span> </span>as
      fatal. Two separate things line up:</p>
    <ol
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">
      <li>An<span> </span><strong>unbalanced quote</strong><span> </span>lets
        a folded header expand into one enormous address token. Postfix
        is arguably being lenient/correct here, but the result is a
        multi-KB "address".</li>
      <li><strong>A failed lookup during<span> </span><em>optional
            header</em><span> </span>canonicalization is fatal to the
          whole message.</strong><span> </span>The junk address erroring
        is fine — it<span> </span><em>should</em><span> </span>fail.
        What's disproportionate is the blast radius:<span> </span><code>header_recipient</code><span> </span>rewriting
        is a cosmetic, best-effort nicety (align a DSN<span> </span><code>To:</code><span> </span>for
        RFC 3464), yet its failure cancels a message whose envelope
        recipient is perfectly valid and deliverable. A cosmetic step
        failing shouldn't take down the delivery.</li>
      <li><strong>A<span> </span><em>permanent</em><span> </span>map
          error (<code>PERM</code>) is reported to the client as a<span> </span><em>transient</em><span> </span><code>451</code>.</strong><span> </span>This
        is what turns a one-off into a queue-wedge: the socketmap said<span> </span><em>permanent</em>,
        but the upstream sees<span> </span><code>451 queue file write
          error</code>, so it retries the identical,
        deterministically-failing message for the entire queue lifetime.
        Had the same condition surfaced as a<span> </span><code>5xx</code>,
        the message would simply bounce and the queue would drain —
        annoying, but not self-perpetuating.</li>
    </ol>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">And
      here's the part that motivated writing to you:<span> </span><strong>postfix
        gives an operator no lever to prevent its own doomed query.</strong></p>
    <ul
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">
      <li>There's no introspection point between "cleanup extracted this
        header address" and "cleanup sends it to the map" — I can't see
        or cap the key that's about to be sent.</li>
      <li>Canonical/socketmap lookups have<span> </span><strong>no
          key-length limit</strong>; cleanup will happily emit a key it
        must know the socketmap can't accept.</li>
      <li><code>header_checks</code><span> </span>match a<span> </span><strong>whole
          logical header</strong>, not individual addresses, so I can't
        write a rule like "reject if any single address in To/Cc exceeds
        N bytes or X symbols." I can only pattern-match the header text
        as a blob, which is fragile against folding.</li>
      <li>The failure surfaces as a generic<span> </span><code>451 ...
          queue file write error</code><span> </span>— indistinguishable
        from disk-full, a database outage, etc. — so I can't even
        intercept<span> </span><em>this specific</em><span> </span>condition
        and handle it differently.</li>
    </ul>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Net:
      the only place I<span> </span><em>can</em><span> </span>currently
      block this is a milter/content filter in front of the relay (which
      is what I'll do), but that feels like papering over a postfix-side
      sharp edge — and it's a leaky patch, because a milter parses
      headers<span> </span><em>very</em><span> </span>differently from
      postfix's<span> </span><code>tok822</code>. The filter's address
      parser doesn't reproduce the unclosed-quote-swallows-the-fold
      behavior, so what the milter sees as the recipient set and what<span> </span><code>cleanup</code><span> </span>will
      actually hand the socketmap are two different things. I can
      approximate (flag unbalanced quotes / over-long tokens on the raw
      header), but I can't reliably pre-empt postfix's own tokenization
      from outside it.</p>
    <h2 id="questions-possible-directions"
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Questions
      / possible directions</h2>
    <ol
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">
      <li>Should a lookup failure during<span> </span><strong>optional
          header</strong><span> </span>canonicalization (<code>header_recipient</code>)
        be fatal at all? Logging and leaving the header untouched
        (continue, don't cancel) would avoid the whole class of problem
        for cosmetic rewrites.</li>
      <li>Could the canonical/socketmap client<span> </span><strong>cap
          the key length</strong><span> </span>it will send — e.g. if
        the rewritten address exceeds a representable/ configurable
        maximum, treat it as "no match" (skip the rewrite) instead of
        emitting a query the server must reject? Postfix knows the key
        before it sends it.</li>
      <li>Failing that, is there appetite for a<span> </span><strong>per-address
          length guard</strong><span> </span>operators could set (reject
        or skip addresses whose serialized form exceeds N), given<span> </span><code>header_checks</code><span> </span>can't
        express per-address limits today?</li>
      <li>Would distinguishing a socketmap<span> </span><code>PERM</code><span> </span>on<span> </span><em>header</em><span> </span>canonicalization
        from a genuine transport/tempfail — with its own status rather
        than the generic<span> </span><code>queue file write error</code><span> </span>—
        be reasonable, so operators can act on it?</li>
      <li>Most impactful, and independent of the above: should a
        socketmap<span> </span><strong><code>PERM</code><span> </span>map
          to a permanent<span> </span><code>5xx</code></strong><span> </span>rather
        than a transient<span> </span><code>451</code>? The server
        explicitly said<span> </span><em>permanent</em>, yet the client
        is told to retry forever. Surfacing it as<span> </span><code>5xx</code><span> </span>(bounce)
        instead of<span> </span><code>451</code><span> </span>(retry)
        would, on its own, prevent the deterministic queue-wedge — even
        if everything else stayed as-is.</li>
    </ol>
    <h2 id="minimal-reproduction"
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Minimal
      reproduction</h2>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Stock
      postfix + any socketmap that bounds its request (e.g. PostSRSd,
      512-byte payload):</p>
    <pre
style="font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><code><span
    class="hljs-attr">recipient_canonical_maps</span>    = socketmap:inet:<span
    class="hljs-number">127.0</span>.<span class="hljs-number">0.1</span>:<span
    class="hljs-number">10003</span>:reverse
<span class="hljs-attr">recipient_canonical_classes</span> = envelope_recipient, header_recipient
<span class="hljs-attr">remote_header_rewrite_domain</span> = domain.invalid
<span class="hljs-attr">local_header_rewrite_clients</span> = &lt;something that excludes the injecting client, so context = remote&gt;
</code></pre>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Inject
      (clean envelope, junk only in the header) a message whose<span> </span><code>To:</code><span> </span>or<span> </span><code>Cc:</code><span> </span>opens
      a double-quote that is never closed and is followed by ~2 KB of
      folded content. cleanup issues an oversized socketmap query, gets<span> </span><code>PERM</code>,
      and cancels the message with<span> </span><code>451 4.3.0 Error:
        queue file write error</code>. Balancing/closing the quote (so
      the token stays a normal-length address) makes it deliver normally
      — confirming size, not content, is the fault.</p>
    <p
style="font-family: &quot;Times New Roman&quot;; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Thanks
      for postfix, and for reading this far.</p>
    <pre class="moz-signature" cols="72">-- 
Regards,
Dmytro Alieksieiev
DevOps Engineer</pre>
  </body>
</html>

--------------Pua1mHggK15LbjXHqJf4F02b--

--===============6417191368454346579==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
Postfix-users mailing list -- [email protected]
To unsubscribe send an email to [email protected]

--===============6417191368454346579==--