2

I'm taking steps to harden my outgoing postfix SMTP server. I want to prevent users from spoofing their sender address.

When sending email with postfix SMTP the sender is identified in three ways:

  1. In the email itself there is a From: header - as defined in RFC 822
  2. When sending with SMTP there is a MAIL FROM address - as defined in RFC 821
  3. When logging into postfix (SMTP) a client specifies a username which can also be interpreted as the sender

Looking in the manual, I believe that reject_sender_login_mismatch and reject_unlisted_sender will together guarantee that the MAIL FROM email as associated with the postifx login. That's 2 and 3 above match.

I'm stuck on how to ensure that all three match. I'm happy if the result just re-writes the message rather than rejecting but either would be fine. However I don't see a way to do this. I can see that cleanup will add a missing From: header but I see no way to check and rewrite one.


Thanks to those who pointed out another similar question. Unfortunately this has been of no help as the question has two answers, one is very vague the other appears to be wrong.

1

I've spent some time looking into this and I can't find a pure postfix way. The one suggestion of using header_checks doesn't seem to work because you can't (AFAIK) match one header against another.

What is possible is to write your own program / script to perform the check for you and use this as a content_filter.


Step 1

Here's a simplified version of the one I use written in python. This reads an email from the stdin taking MAIL from and RECPT to addresses as arguments. It checks the From: header against the MAIL from and replaces it if they don't match. I save this as /usr/share/mail_filter/filter.py:

#! /usr/bin/python3

import sys
import subprocess
import email.parser
import email.policy

parser = email.parser.BytesParser(policy=email.policy.default)
message = parser.parse(sys.stdin.buffer)

envelope_from = sys.argv[1]
envelope_to = sys.argv[2]
# This if statement checks envelope from against message from
if message['From'].addresses[0].addr_spec != envelope_from:
    print(f"Replacing sender {message['from']} with {envelope_from}", file=sys.stderr)
    message.replace_header('From', envelope_from)

# sendmail -G -i -f sender@sender_domain.tld recipient@recipient_domain.tld
sendmail = subprocess.Popen(["/usr/sbin/sendmail", "-Gif", envelope_from, envelope_to], stdin=subprocess.PIPE)
with sendmail.stdin:
    sendmail.stdin.write(message.as_bytes())

# If sendmail returns an error, cascade it back to postfix
sys.exit(sendmail.wait())

Step 2

Updated /etc/postfix/master.cf:

# added the -o content_filter...
smtp inet n       -       y       -       -       smtpd
  -o content_filter=filter:dummy

# Added this entire service
filter    unix  -       n       n       -       10      pipe
  flags=Rq user=filter null_sender=
  argv=/usr/share/mail_filter/filter.py ${sender} ${recipient}

Note this references the script location from step 1 and the user defined in step 3

Step 3

I added an OS user filter in a group with access to use sendmail.

useradd --system --gid postdrop filter

Caveats

As per the instructions simple filters (passing forward using sendmail) can only be applied to SMTP clients and cannot filter messages sent via sendmail. If this is a problem for you then you would need to write an advanced content filter.

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.