[exim] Counting the number of headers in an incoming email (…

Top Page
Delete this message
Reply to this message
Author: Chris Siebenmann
Date:  
CC: exim-users, cks
Old-Topics: [exim] Limit the total number of headers in incoming SMTP messages?
New-Topics: Re: [exim] Counting the number of headers in an incoming email
Subject: [exim] Counting the number of headers in an incoming email (was Re: Limit the total number of headers in incoming SMTP messages?)
Back a long time ago, I wrote:
> We use a commercial anti-spam system behind our Exim-based inbound MX
> gateway that turns out to be unhappy if a message has 'too many' headers
> (where this is an undocumented value of more than 512 headers). I'd like
> to detect and reject these messages in the DATA phase, rather than having
> them accepted, passed to the anti-spam system, and die quietly.
>
> Given that the 'regex' ACL condition is specifically restricted to
> handling a single line, about the only way to do this that I can see
> is to (ab)use Exim's content scanning interface to run a program that
> counts header lines and emits a suitable 'I found bad stuff' message
> when it finds too many headers that the DATA ACL will then use to reject
> the message. This seems a little bit brute force (among other things,
> it means running an external program on every incoming messages).
>
> Can anyone think of a better way to do this?


The core problem here is to count the number of headers (not the
number of header lines[*]) during the SMTP DATA ACL. People at the time
put forward a number of suggestions for counting the number of header
lines, all of which work(ed) but which weren't quite what we wanted.
I dropped the problem at the time but recently took another run at it
and to date this is what I have come up with (in expanded form so it is
easier to follow):

  deny
     # Use ${sg} to merge continued header lines together into
     # one line by turning newline-whitespace into just a space:
     set acl_m0_minhdr = ${sg{$message_headers} {\N\n\s\N} { }}


     # Count the number of (remaining) newlines in the minimized
     # result; this is the number of actual headers:
     set acl_m0_hdrcnt = ${reduce{<\n $acl_m0_minhdr} {0} {${eval:$value+1}} }


     # In modern versions of Exim I believe this would just be:
     #    ${listcount<\n $acl_m0_minhdr}


     condition = ${if >{$acl_m0_hdrcnt} {MAXHEADERCOUNT} {yes}}
     message = This message has too many headers


In testing this appears to work and give correct results (once I
remembered that Exim inserts a Received: header of its own on top
of the headers I was sending it in test messages).

Let me know if people think that I'm overlooking something or
could be doing this in a more clever way. Otherwise, well, maybe
this can be of use to someone else someday. If nothing else, coming
up with this was an interesting exercise in how to exploit Exim's
string operations to do something that's probably relatively outside
the sort of things that they were intended for.

(I tried versions using some other string operations but found that this
was the easiest approach. Another option for us would have been to do
the hard work in a Perl function, likely guarded based on a check of
$message_linecount - $body_linecount being large enough that it was at
least possible for the header limit to be exceeded. In the end I decided
I'd rather do it in pure Exim, if only for simplicity as this would have
been our first use of Perl in our Exim setup.)

    - cks
[*: I tested and our anti-spam system really does specifically care
    about the number of headers. It is happy with messages that have
    more header lines but fewer headers than its trigger point.]