At work we recently began receiving emails with what looks like malicious code contained in the email body. I'm trying to create a procmail recipe that will use regex to match only the non-word characters listed in a long string like this one:


Note there are no spaces. This is a small example, some emails have lines over 20,000 characters long. This is messing with our spam filtering and I'm hoping there's a way to match long lines of non-word characters with no whitespace separators. So far I've tried using several variants/pieces/combinations of what is below with only partial success - I'm having trouble finding a way to make this work without also catching a lot of false-positives:

* B ?? ^.*(!@#$%^&*()[]{})+ && ! </([a-z|A-Z])/>

I was thinking that if I can make a regex that matches, then I can combine that with another rule that sends the email to a separate directory if the total characters per line exceed a certain amount, say 500. Haven't got that part figured out yet...other regex I've used in the past had the \W flag that matched only non-word characters, does procmail have an equivalent? How can I do this?

  • Do you run your own email server, or are you filtering as a client only? ... The solution you are working on may filter out all non-text attachments and/or inline images. Is there any need for viewing company email in html mode, or is plain text just fine? Are the company email clients viewing their email via a web browser or a separate client like MS Outlook or Claws or Thunderbird? Oct 23 '17 at 14:24
  • We administer the servers, however they are shared machines so the procmail filtering is set up per-account in most cases. In other words I personally can't use anything other than the /home/user/.procmailrc file or make system-wide changes to procmail.
    – lennys
    Oct 23 '17 at 14:33
  • Whoops, hit Enter too soon. The emails in question will only show the long lines in plain-text mode, the html mode just shows a link to a malicious web page as its payload. These malicious emails have text that's unique enough that I think I can send them to the bitbucket if I can get a working recipe, so the clients ideally would never see these emails at all. I don't have any knowledge of what email client the end-user has, for the moment I'm just using the webmail interface since that seems to be the most common tool for our customers.
    – lennys
    Oct 23 '17 at 14:41

Regarding your last question about \W, you don't need it. You can just do the inverse of \w. For example, if you consider this to be word characters [a-zA-Z0-9_] (that's Vim's definition of \w, for example) then use [^a-zA-Z0-9_].


The superficial fix of your attempt would be

*   B ?? [][!@#$%^&*(){}]+
* ! B ?? </[a-zA-Z]+/>

if I am guessing correctly that you are trying to match a sequence of two or more of the punctuation characters, and a sequence of characters which are alphabetics between </ and /> as a negated condition (meaning a match if present prevents the recipe from matching).

However, any two punctuation characters (like just !!) will match the first regex; and I don't see why you articulate the second condition like you did.

A challenge is that many punctuation characters are regex metacharacters. To match a literal [ or ( or * you need to backslash-escape them, or put them in a character class. (A character class containing [ is [[] which takes a while to wrap your head around. That's a literal [ inside the character class brackets [...]. And a character class containing ] and [ needs to have them in this order, so [][].) I made the first regex simple by putting everything in a character class, but as it is, it is probably too broad. I would perhaps require there to be a sequence of, say, four or five in sequence. (Unfortunately, Procmail's regex variant doesn't support egrep's [class]{5,} numeric quantifier; it would be handy here.)

* [][!@#$%^&*(){}][][!@#$%^&*(){}][][!@#$%^&*(){}][][!@#$%^&*(){}][][!@#$%^&*(){}]
* ! </[a-zA-Z]+/>

If you look carefully, you see that the first one is [][!@#$%^&*(){}] repeated five times. If you want to make it longer (like, 500 adjacent occurrences) just repeat as many times as necessary. If the entire expression is longer than LINEBUF, you'll need to watch out for that as well -- if you want to make room for a seven-character regex 500 times, that's 10,200 bytes, so you need to make sure LINEBUF is at least that big before the recipe which contains this long expression.

Notice also how a B flag on the recipe removes the requirement to put B ?? on each individual recipe line.

If you want to compare the message's length, simply use > and the number you want. With the :0B flag, * > 512 will match a body which is longer than 512 bytes.

For your final question, \W is basically equivalent to the character class [^A-Za-z0-9_] (a single character which is not in the range A-Z or the range a-z or the range 0-9 or the character _).

  • I already answered OP's final question.
    – B Layer
    Oct 23 '17 at 14:48
  • @BLayer I saw your partial answer but I like for this to be self-contained ... and anyway, I wrote it before I reloaded the page to see your answer.
    – tripleee
    Oct 23 '17 at 14:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.