The art of setting up a sendmail server on Debian 8

This post was written by eli on March 30, 2019
Posted Under: email,Internet,Server admin

But why?

Fact number one: Running your own mail server is the most likely cause for messing up, and that can mean an intrusion to the server or just turning it into a public toilet for spam.

Nevertheless, if mail delivery is important to you, there’s probably no way around. And I’m not talking about the ability to mass-mail. Even having plain, manually written, messages delivered to that semi-security-paranoid company, even if it has a ZIP attachment, can be a challenge. And no matter what ISP you have or other paid-for mail relay, there will always be someone else pushing junk through the same channel, and make the used mail relay’s reputation questionable.

And I’m also under the impression that paid-for mail relays won’t send you a bounce message if the destination server refuses to talk with them. Once I got my own running, I suddenly got a few of these. I now realize how some emails I sent in the past just vanished.

Not to mention that the emails reach their destination much faster with a private workhorse.

The key issue is to take control of your reputation. As simple as that. Use all possible means (detailed below) to ensure the recipient that it was you who sent it, and let the lack of blacklisting do the rest.

But, ehm, after all this preaching, the real reason I set up my own mail server was that I had no choice: My web host, which also took care of outgoing mail from my website drove me crazy with upgrades out of nowhere. So I went for VPS hosting, and that requires your own mailing server. For better and worse.

Port 25 might be blocked by ISP

This isn’t directly related, but important enough: My ISP, Netvision, blocks connection to port 25 from my computer, probably to avoid blacklisting of their IP addresses due to spamming from them.

This means that testing port 25 from my local computer is worthless and misleading. I suppose other ISPs do the same.

Use external tools for testing port 25.

Selecting server software

Debian 8 arrived with Postfix by default. Exim is popular. I’m used to qmail and sendmail. Difficult to choose. Security is important. If the server gets compromised, my domain turns into a spamhouse at best. I also need some advanced features (DKIM in particular).

I went for sendmail 8.14.4. It has a bad word of mouth, but its security advisory record over the last ten years is better than Postfix and surely better than Exim. That’s a surprise, but you can’t argue with facts.

And I could go for qmail there, but it seems like it needs patching to support DKIM, and then who knows if I haven’t just made a hole.

Goals

The server should

  • Open ports 25 and 587 for anyone to connect.
  • Relay any email received on ports 25 and 587 from localhost only, without authentication
  • Accept emails to local recipients ports 25 and 587 when connecting from foreign host. Port 25 is essential for inbound mail, but is sometimes blocked by firewalls, so open the other port as well.
  • Add a DKIM signature to emails going to foreign hosts only
  • Refuse to VRFY and EXPN
  • Accept all emails (from external mailers as well) even if they don’t have rDNS entries etc (let the spam filter handle them)

Checklist

Note that a lot of these items are detailed further down this post. I also suggest taking a look on my post on SPF, DKIM and DMARC and possibly this, this and this as well.

  • Verify that neither your IP nor your domain name have a bad reputation with a blacklist check.
  • Make an rDNS record for the mail server’s IP. It should better begin a “mail” or “smtp” or “mx” subdomain. Make sure there’s only one rDNS record with a reverse DNS lookup (e.g. dig -x). Sounds silly, but happened to me.
  • Set up the mail server properly and safely. Run a security check.
  • Set up the firewall to kill any IPv6 traffic (in particular reject, not drop, outgoing packets)
  • Create SPF, DKIM and DMARC DNS record for the server. For SPF, with and without the “mx” subdomain.
  • If the server has another name internally (other than the mx subdomain), make sure it has an A DNS record as well as an SPF one.
  • Verify that the outgoing mail goes out right with this DKIM validator, which allows sending mail to it, and then see exactly how it arrived + results on the validation. Invaluable.
  • Run a verbose manual mail submission and verify everything makes sense. In particular, make sure the HELO/EHLO domain matches the rDNS. However don’t expect the EHLO on the internal submission (from the program we’re running to the local server) to be the externally known one.
  • Validate the DMARC DNS record for your domain by sending a test email to autoreply@dmarctest.org (or any other one listed here). My anecdotal experience is that Gmail refused to accept mail (as in “Service unavailable” SMTP rejection) from a domain until I added a DMARC record.
  • Check any programs (web applications in particular) than send email, and verify that the envelope sender (MAIL FROM) makes sense (preferably the same as the From header). Best to send mail to some Gmail account, and see what it found the smtp.mailfrom to be. It it’s not a legal domain there, Gmail refuses to accept the mail.
And then make friends with those who have a say on spam detection:
  • Query your IP’s status at SPFBL and possibly delist it from the blacklist. It requires a working MTA on the server with postmaster being user on the domain. Spamassassin relies on this service.
  • Register the domain at Gmail’s Postmaster Tools to solve delivery problems to Gmail if such occur. I also have a feeling that this might reduce Gmail’s spam rating of the domain (it’s like someone takes responsibility for it).
  • Join their Smart Network Data Service (SDNS) and Junk Mail Reporting Program. Be sure not to be listed on Microsoft’s IP blacklist (or delist your IP address anyhow, just to be safe). For starter info, go here. On their troubleshooting page for postmasters, it says new IPs are likely to have issues. They suggest getting a certification from Return Path and/or creating a Microsoft account, and get a “wealth of information”.
    Note that this is important for delivery of mail to any institution that has made the mistake of relying on Microsoft’s mail infrastructure. A proper delisting takes you from 

    Mar 11 20:18:23 sm-mta[5817]: x2BKIL2H005815: to=<xxxxxxx@mit.edu>, delay=00:00:02, xdelay=00:00:02, mailer=esmtp, pri=121914, relay=mit-edu.mail.protection.outlook.com. [104.47.42.36], dsn=5.7.606, stat=User unknown

    (but the bounce message indicated that it’s not an unknown user, but a blacklisted IP number) to

    Mar 11 21:15:12 sm-mta[6170]: x2BLF8rT006168: to=<xxxxxxx@mit.edu>, delay=00:00:03, xdelay=00:00:03, mailer=esmtp, pri=121915, relay=mit-edu.mail.protection.outlook.com. [104.47.42.36], dsn=2.0.0, stat=Sent (<5C86CFDC.6000206@example.com> [InternalId=11420318042095, Hostname=DM5PR01MB2345.prod.exchangelabs.com] 11012 bytes in 0.191, 56.057 KB/sec Queued mail for delivery)

Setting up sendmail

Important general note: Sendmail is made to work sensibly out of the box. It’s clever enough to relay any mail received from localhost to external servers, and not to do that with mails from external connections. Unless you explicitly tell it to become a spam relay. The default configuration files are installed with apt are fine and probably secure.

Sendmail’s internals, on the other hand, with all macros and stuff, is completely horrible.

So the trick is to make minimal changes. There really isn’t much that needs to be done. For a fairly regular mail configuration, there is very little to do (on Debian 8, that is).

So first, install it:

# apt install sendmail

Not just sendmail-bin. It won’t work. Don’t install rmail — it’s for UUCP. Which is ancient and disabled anyhow.

Now changes in the configuration file. By default on Debian 8, sendmail listens to port 25 and 587 at IPv4′s localhost only, and relays mails to external servers as necessary. In order to open ports 25 and 587 for incoming mail to local addresses only from any host, change the line in /etc/mail/sendmail.mc saying

DAEMON_OPTIONS(`Family=inet,  Name=MTA-v4, Port=smtp, Addr=127.0.0.1')dnl
DAEMON_OPTIONS(`Family=inet,  Name=MSP-v4, Port=submission, M=Ea, Addr=127.0.0.1')dnl

to

DAEMON_OPTIONS(`Family=inet,  Name=IPv4-port-25, Port=smtp, M=E')dnl
DAEMON_OPTIONS(`Family=inet,  Name=IPv4-port-587, Port=submission, M=E')dnl

Let’s explain the changes:

  • Most important, the “Addr=” part was dropped, meaning connections from any host is allowed. Sendmail isn’t stupid: If the connection is from localhost, the destination can be any (including relaying to any host on the web), but if it comes from anywhere else, it’s for local addresses only. So we don’t turn into a spam machine. In other words, this is what a session with an external client looks like:
    >>> MAIL FROM:<sender@nowhere.com>
    <<< 250 2.1.0 <sender@nowhere.com>... Sender ok
    >>> RCPT TO:<anybody@not-here.com>
    <<< 550 5.7.1 <anybody@not-here.com>... Relaying denied
  • It’s “M=E” for both. Note that the “a” part was dropped, so access is without authentication. It’s intended for anyone to drop mails to local users. On the other hand “E” prevents ETRN on both, as they are both exposed.
  • The change in “Name”. Well, it’s just a name with the sole purpose of appearing in the logs on the “daemon=” part. So it better say something meaningful to humans, like which port the connection took place on.

This is a good time to mention that in sendmailish, it’s as if there were two separate MTA daemons, one for each port. This is the terminology used in the log.

Quote at the top of the file, after the DOMAN() assignment, I added a

define(`confDOMAIN_NAME', `mx.example.com')dnl

This sets the sendmail’s host name, as presented while talking to clients, in particular on HELO/EHLO (there is no need to set the confHELO_NAME / HeloName option). Even if it happens to give the correct name without it, I would set it like this. It’s crucial that identifies itself with the name it’s expected to give, or SPF checks can fail.

And of course, set it to the rDNS of your IP address, not mx.example.com.

Setting up “virtual users”

Having email addresses that don’t match any actual user names on the machine requires defining “virtual users”. But first, it’s essential to tell sendmail to accept emails to other domains than its own. To do this, add one line for each domain. If there are subdomains, add one line for each as well (by default, sendmail wants this explicitly). So I added the following line to /etc/mail/local-host-names:

billauer.co.il

This makes sendmail consider these domains local. An important side effect of this is that now root@billauer.co.il is a legal alias for the local root account. This is an often guessed address by spammers. Handled below.

Then enable virtual users. I put this after the other FEATURE statements in/etc/mail/sendmail.mc:

FEATURE(`virtusertable')dnl

And then run “make” under /etc/mail to update sendmail.cf. And restart sendmail.

Finally, prepare a file with a list of mail addresses, and to which read user they should be routed. First column in the mail address, the second is the target. For simplicity, keep the second column with real local users, but it’s also possible to use other first-column entries as the target. By why messing.

This goes to the file named /etc/mail/virtusertable. This is what it could look like:

someone@billauer.co.il		root
not-me@billauer.co.il		root

And then call “make” under /etc/mail, which updates /etc/mail/virtusertable.db. There is no need to restart sendmail to make the changes in virtusertable.db take effect.

Mail addresses as well as domains are case-insensitive, of course. But there are no shortcuts with subdomains: Everything after the “@” must match.

Now preventing spammers from sending mails to root@billauer.co.il. Just add this line to /etc/mail/virtusertable:

root@billauer.co.il                     error:nouser User unknown

This causes sendmail to reject the mail address flat at connection:

Apr 23 08:29:05 sm-mta[12752]: x3N8T4Sn012752: <root@billauer.co.il>... User unknown

But what happens if an internal mail to root is sent, from some cron job, for example? Is it rejected as well? That wasn’t the purpose. Well, on my machine this isn’t a problem, because these mails are sent to root@theserver.billauer.co.il (as defined in /etc/hosts?), so they’re not caught by the virtual user rule above. I don’t know what the result would be without this subdomain thing.

Rejecting IPv6

Why? Because IPv6 is where everything gets messy. Sendmail is already configured not to listen to IPv6, but then, when it’s about to relay to another server, things get ugly. In particular with Gmail, which supplies an IPv6 AAAA DNS entry for its MX servers.

The problem is that sendmail first attempts IPv6, no matter what (see Nov 30 2018 remark after some discussion on this page).It seems to be an Microsoft-style attempt to push IPv6 by forcing everyone to use it. I would have compiled sendmail myself to get rid of this “feature”, but there’s an easier way. So my own attempt to add a

CLIENT_OPTIONS(`Family=inet')dnl

in the sendmail.mc file, turning into

O ClientPortOptions=Family=inet

in sendmail.cf, didn’t make any difference. It should have turned IPv6 off, but didn’t: Sendmail tries IPv6 first, fails, among others because my firewall kills all incoming IPv6 packets, and after a minute goes for IPv4. So why wait?

My solution was to set the firewall to reject the outgoing IPv6 packet, so any TCP connection gets an immediate RST. This doesn’t prevent sendmail from trying IPv6, but makes it clear it’s a no-go. So it doesn’t waste time on it.

These are my firewall rules for that. It’s the OUTPUT rules that I added specially for sendmail:

# ip6tables -A INPUT -i lo -j ACCEPT
# ip6tables -A INPUT -j DROP
# ip6tables -A OUTPUT -o lo -j ACCEPT
# ip6tables -A OUTPUT -j REJECT --reject-with icmp6-addr-unreachable

Reviewing sendmail’s setup

For the real masochists out there, open /etc/sendmail.cf.

  • Lines starting with # are comments, of course.
  • Lines starting with “O” are options.
  • Searching the file for “=/” reveals all file-relating settings (because it’s an assignment followed by the beginning of an absolute path)

Setting up DKIM

I have a separate post on DKIM and friends. Better take a look if this is Chinese to you.

opendkim is made to work sensibly. It is inserted as a mail filter (“Milter”) for sendmail, making it sign outbound messages, and check inbound messages. As with sendmail, there are a few things to set up, and it’s good to go.

Following this guide (more or less). And man opendkim.conf, which is good. First, install:

# apt install opendkim opendkim-tools

Then create the keys:

# mkdir -p opendkim/keys/billauer.co.il
# opendkim-genkey -D /etc/opendkim/keys/billauer.co.il/ -d billauer.co.il -s dkim2019
# chown -R opendkim:opendkim /etc/opendkim/keys/

Now Configuration. The only changes I needed to make from the default files were: Edit /etc/default/opendkim, adding the following line at the end, so a TCP port is opened:

SOCKET="inet:8891@localhost" # listen on localhost port 8891

and since I need to sign multiple domains, added these two lines to /etc/opendkim.conf

KeyTable		refile:/etc/opendkim/KeyTable
SigningTable		refile:/etc/opendkim/SigningTable

and added the two following files. /etc/opendkim/KeyTable reading

dkim2019._domainkey.billauer.co.il billauer.co.il:dkim2019:/etc/opendkim/keys/billauer.co.il/dkim2019.private
dkim2019._domainkey.example.com example.com:dkim2019:/etc/opendkim/keys/example.com/dkim2019.private

and /etc/opendkim/SigningTable:

*@billauer.co.il dkim2019._domainkey.billauer.co.il
*@example.com dkim2019._domainkey.example.com

For a server whose outbound messages come only from localhost, there’s no need to set neither InternalHosts nor ExternalIgnoreList, as this is the default. These appear in a lot of tutorials.

Finally, make the DKIM a mail filter (“Milter”) on sendmail by adding this line at the end of sendmail.mc (and run “make” + restart sendmail):

INPUT_MAIL_FILTER(`opendkim', `S=inet:8891@127.0.0.1, F=T')

Note the “F=T” part. It will make sendmail refuse to accept mails if the DKIM server isn’t responding properly with a

451 4.3.2 Please try again later

The default is to pass the mail through without the milter if it doesn’t work, which would mean sending unsigned mails without paying attention. The backside of this is that no mail will arrive either if this happens, but at least the delivery won’t fail completely (assuming the issue is resolved within a day or so).

Don’t forget to set up the TXT DNS records with the *.txt files generated with opendkim-genkey. These files are written in zone format for the bind daemon. The actual text is the concatenation of the two strings in quotation marks, after removing these quotation marks. Think of it as a multi-line string in C language.

All done? Use this DKIM validator to see exactly how well it went.

Remove outbound messages from the mailing queue

# mailq
MSP Queue status...
/var/spool/mqueue-client is empty
		Total requests: 0
MTA Queue status...
		/var/spool/mqueue (2 requests)
-----Q-ID----- --Size-- -----Q-Time----- ------------Sender/Recipient-----------
x22FdWV1010569     1864 Sat Mar  2 10:39 MAILER-DAEMON
                 (Deferred: Connection timed out with server.com.)
					 <ze@server.com>
x22FMGAi009668       17 Sat Mar  2 10:23 <this@there.com>
                 (Deferred: Connection timed out with example.com.)
					 <eli@example.com>
		Total requests: 2
# cd /var/spool/mqueue
# rm *x22FdWV1010569
# rm *x22FMGAi009668
# systemctl restart sendmail

Gmail won’t talk with anyone

Gmail’s server doesn’t respond to a SYN at port 587 or 25, and won’t talk to you unless you have an rDNS. Only after having the rDNS set on the server:

# nc gmail-smtp-in.l.google.com. 25
220 mx.google.com ESMTP y6si2100605wmi.83 - gsmtp

And that’s just the beginning. Without having DMARC set up, it wouldn’t relay my mails. More on DMARC here.

Sources of information

Add a Comment

required, use real name
required, will not be published
optional, your blog address