Run your own email server, part 3

In this how-to we'll set up an email server from scratch using NetBSD.

Not the worst SSL certificate HOW-TO

For part 3, we'll get right in to the steps needed to set up OpenSSL and create your CSR (certificate signing request). This won't be hard since you've already decided on the name of your server.

cp /usr/share/examples/openssl/openssl.cnf /etc/openssl
cd /etc/openssl/private
openssl req -new -newkey rsa:2048 -nodes -keyout vax_zia_io.key -out vax_zia_io.csr

Note that 2048 bits is used because the machine in this HOW-TO is relatively slow, and we don't want connections to time out while waiting for the VAX to do work on its end. For anything modern (that is, 500 MHz or faster), 4096 bits is recommended. Also note that we're going to create an SSL certificate specifically for, based on what was talked about in part 2.

After a little while, depending on the speed of your computer, you'll be asked for the following:

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:California
Locality Name (eg, city) []:Los Angeles
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Zia Retrocomputing
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []
Email Address []: john {at}

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Note that the common name MUST be the name that matches the certificate we're requesting. If that's wrong, then people will get certificate errors, which wouldn't be fun and would defeat the purpose of getting a certificate. Also, the email address must be real and must work, and where I put "{at}" is where an "@" would usually go.

Next, go to and buy the cheapest Comodo PositiveSSL Certificate. I do not have any reasons to think that or Comodo are either bad or good (well, Comodo has done some shady things, but I can't think of a certificate provider who hasn't), but I've chosen them because they're cheap and easy.

In my example, I've bought three years for $15 (US currency). After payment, they ask how to do authentication (to prove you control the domain) and ask for the CSR (certificate signing request). I chose HTTP, then pasted the CSR from the output of catting vax_zia_io.csr.

They optionally ask you to select your server, but the certificates you get using the default (Apache-ModSSL) will work just fine with OpenSSL, IMAP-UW and Sendmail.

On the next page, make sure the name you want matches what they have, fill in the contact information, agree to the agreements, and click continue.

Next, wait for the email they'll send you, or if you chose HTTP authentication, download the auth file and install it.

In the time it took me to heat a frozen vegetable pot pie, the request was authenticated! On the Order Details page there is now a Download Certificate button. Download it. scp it to your machine. Unzip it in to /usr/openssl/private/. You'll now have these files:


To make the intermediate certificate (chain file) and make the files not readable by other users, do the following:

cat COMODORSADomainValidationSecureServerCA.crt COMODORSAAddTrustCA.crt > intermediate.crt
chmod 400 vax_zia_io.crt vax_zia_io.key
chmod 444 intermediate.crt AddTrustExternalCARoot.crt COMODORSA*

Also, we'll fetch a certificate revocation list like so:


Just for reference, this VAXstation 4000/60 is not horribly fast by modern standards, but it's still better than most people might assume a 1991 machine would be. Here's the results of running openssl speed rsa:

Doing 512 bit private rsa's for 10s: 38 512 bit private RSA's in 10.13s
Doing 512 bit public rsa's for 10s: 358 512 bit public RSA's in 10.14s
Doing 1024 bit private rsa's for 10s: 8 1024 bit private RSA's in 11.20s
Doing 1024 bit public rsa's for 10s: 137 1024 bit public RSA's in 10.18s
Doing 2048 bit private rsa's for 10s: 2 2048 bit private RSA's in 17.27s
Doing 2048 bit public rsa's for 10s: 43 2048 bit public RSA's in 10.08s
Doing 4096 bit private rsa's for 10s: 1 4096 bit private RSA's in 57.37s
Doing 4096 bit public rsa's for 10s: 13 4096 bit public RSA's in 10.71s
OpenSSL 1.0.1u  22 Sep 2016
options:bn(32,32) md2(int) rc4(ptr,int) des(idx,cisc,2,long) aes(partial) idea(int) blowfish(idx) 
gcc version 4.1.3 20080704 (prerelease) (NetBSD nb3 20111107)
                  sign    verify    sign/s verify/s
rsa  512 bits 0.266579s 0.028324s      3.8     35.3
rsa 1024 bits 1.400000s 0.074307s      0.7     13.5
rsa 2048 bits 8.635000s 0.234419s      0.1      4.3
rsa 4096 bits 57.370000s 0.823846s      0.0      1.2

Compare these results to the results from your computer. I'm sure your $8 Pogoplug or $9 C.H.I.P. will seem like a waste of resources unless you're running on an m68k Mac or one of the few remaining NS32k systems.

Finally, we're configuring Sendmail

Ok. Let's set up Sendmail. After installing the Sendmail package from pkgsrc, it told you to do these things. If you didn't, then do them now:

ln -fs /usr/local/share/examples/sendmail/mailer.conf /etc/mailer.conf
echo "./etc/mailer.conf type=link mode=0444" >> /etc/mtree/special.local

Next, we're going to create our sendmail mc file, which in turn is used using m4 to create our file. The file follows. I've named it (since the machine's name is vax) and put it in to /etc/mail/. The version is arbitrary - I guess I've gone through six refinements since the mid 1990s:

# John Klos, jklos {at}, 26-November-2005
VERSIONID(`@(#)  $Revision: 7 $')dnl
define(`confPRIVACY_FLAGS', `goaway,nobodyreturn')dnl
define(`confMAX_HEADERS_LENGTH', 32766)dnl
define(`confCONNECTION_RATE_THROTTLE', 3)dnl
define(`confBAD_RCPT_THROTTLE', `1')dnl
define(`confMAX_MESSAGE_SIZE', 10485760)dnl
define(`confMAX_DAEMON_CHILDREN', 5)dnl
define(`confSMTP_LOGIN_MSG', `$j Sendmail $v/$Z; $b.  By connecting to this server, you agree to be open relay tested.')dnl
define(`confDELAY_LA', 6)dnl
define(`confQUEUE_LA', 10)dnl
define(`confREFUSE_LA', 14)dnl
define(`confLOG_LEVEL', 14)dnl
define(`SMTP_MAILER_MAXRCPTS', 1)dnl
dnl # The following DOMAIN_NAME is set so that the name matches our SSL cert.
define(`confDOMAIN_NAME', `')dnl
define(`confMILTER_MACROS_CONNECT', `H, j, _, {daemon_name}, {daemon_port}, {if_name}, {if_addr}, {client_addr}')dnl
define(`confMILTER_MACROS_HELO', `{verify}, {cert_subject}')dnl
define(`confMILTER_MACROS_ENVFROM', `i, {auth_authen}')dnl
dnl # SASL SMTP AUTH stuff
define(`confAUTH_MECHANISMS', `PLAIN')dnl
define(`confAUTH_OPTIONS', `A,p,y')dnl
define(`confTLS_SRV_OPTIONS', `V')dnl
define(`CERT_DIR', `/etc/openssl/private')dnl
define(`confCACERT_PATH', `CERT_DIR')dnl
define(`confCACERT', `CERT_DIR/intermediate.crt')dnl
define(`confSERVER_CERT', `CERT_DIR/vax_zia_io.crt')dnl
define(`confSERVER_KEY', `CERT_DIR/vax_zia_io.key')dnl
define(`confCLIENT_CERT', `CERT_DIR/vax_zia_io.crt')dnl
define(`confCLIENT_KEY', `CERT_DIR/vax_zia_io.key')dnl
define(`confCRL', `CERT_DIR/revoke.crl')dnl
define(`ALIAS_FILE', `/etc/mail/aliases')dnl
FEATURE(`virtusertable', `hash -o /etc/mail/virtusertable')dnl
FEATURE(`genericstable', `hash -o /etc/mail/genericstable')dnl
FEATURE(`mailertable', `hash -o /etc/mail/mailertable')dnl
FEATURE(`domaintable', `hash -o /etc/mail/domaintable')dnl
FEATURE(`access_db', `hash -T<TMPF> /etc/mail/access')dnl

dnl # Blocklists
FEATURE(`dnsbl', `', `"SPAM blocked. See:" $&{client_addr}')dnl
FEATURE(`dnsbl', `', `"SPAM blocked: " $&{client_addr} " rejected - found in see"')dnl
FEATURE(`enhdnsbl', `', `"SPAM blocked. See:"$&{client_addr}', `t')dnl

dnl # Require connecting server's name resolve in DNS and match connecting IP

dnl # For milter-greylist
INPUT_MAIL_FILTER(`greylist', `S=local:/var/milter-greylist/milter-greylist.sock, T=R:1m')dnl

dnl # For opendkim
INPUT_MAIL_FILTER(`opendkim', `S=local:/var/run/opendkim/opendkim.sock')

DAEMON_OPTIONS(`Port=smtp, Family=inet, address=, Name=MTA')dnl
DAEMON_OPTIONS(`Port=smtp, Family=inet6, address=::, Name=MTA6, Modifiers=O')dnl
DAEMON_OPTIONS(`Port=smtps, Family=inet, address=, Name=TLSMTA, M=s')dnl
DAEMON_OPTIONS(`Port=smtps, Family=inet6, address=::, Name=TLSMTA, M=s')dnl

Whew! Next, create /etc/mail/require_dns.m4:

Kcheckdns dns -R A
Kcheckv6 dns -R AAAA
Kcheckptr dns -R PTR
FH /etc/mail/access

# require_dns.m4, 9-November-2003, John Klos (
#   Updated 6-January-2007
#   Checks that HELO IP literal matches connecting machine, and
#   checks that HELO domain name resolves (but not necessarily back
#   to the connecting server), and that it isn't our name.

# This HELO checking is what should be done, anyway; syntactically
#   invalid HELOs are allowed to be rejected. Checking whether a 
#   HELO hostname resolves is my lazy way of checking the syntax.

# localhost (real address, not HELO localhost) is always Ok.

R$*			$: $&{client_addr}
R127.0.0.1		$@
RIPv6:::1		$@
RIPv6:0:0:0:0:0:0:0:1	$@

# Reject our own names. Anything that's in /etc/mail/access or 
#   /etc/mail/local-host-names should not match any HELO name.

R$*			$: $&s
Rlocalhost		$#error $@ 5.1.8 $: "550 Access denied. You are not obviously not localhost."
R$=w			$#error $@ 5.1.8 $: "550 Access denied. You are not "$&s"."
R$=H			$#error $@ 5.1.8 $: "550 Access denied (access file). You are not "$&s"."

# Reject common domain names which all use proper HELO strings.		$#error $@ 5.1.8 $: "550 Access denied. You are not "$&s"."		$#error $@ 5.1.8 $: "550 Access denied. You are not "$&s"."		$#error $@ 5.1.8 $: "550 Access denied. You are not "$&s"."		$#error $@ 5.1.8 $: "550 Access denied. You are not "$&s"."		$#error $@ 5.1.8 $: "550 Access denied. You are not "$&s"."		$#error $@ 5.1.8 $: "550 Access denied. You are not "$&s"."

# This test checks if the HELO string either matches or is the last part of 
#   the PTR record for services such as Hotmail and Gmail.

R$*			$: <$&{client_name}> <$&{client_resolve}>
R<$*$&s> <OK>		$@

# Skip checking if connection is authenticated
R$*			$: <$&{auth_type}>
R<PLAIN>		$@

# These rules assume that all will be OK if:
#   HELO [IP address] matches [{client_addr}] or
#   HELO [IPv6:IPv6 address] matches [{client_addr}]
#   Or, if neither of the above, that the HELO string must be a FQDN (primary
#   host name) as per the RFCs and therefore should resolve. Non-matching
#   [IP address] or [IPv6:IPv6 address] do not resolve as FQDN and fail below.

R$*			$: $&s
R [$&{client_addr}]	$@

# Stick the connecting IP address into the workspace; if it's an IPv6 address,
#   replace it with the IPv6 address of the AAAA HELO lookup. If it matches
#   the connecting IP, it's Ok.

R$*			$: $&{client_addr}
RIPv6:$+		$: <$( checkv6 $&s $: FAIL $)>V6
R<$&{client_addr}>V6	$@

# Stick the IP address of the A HELO lookup into the workspace. Fail
#   if the lookup fails.

R$*			$: <$( checkdns $&s $: FAIL $)>V4
R<FAIL>V4		$#error $@ 5.1.8 $: "550 Access denied. HELO does not resolve. (HELO " $&s ")"
R<$&{client_addr}>V4	$@

It's hard to believe that stuff actually made sense to me at one time... But it works! Read the comments if you'd like an explanation of what it does, but in a nutshell, it checks that the HELO / EHLO name is real.

Now that we've made those two files, make symlinks:

ln -s /etc/mail/ /usr/local/share/sendmail/cf/
ln -s /etc/mail/require_dns.m4 /usr/local/share/sendmail/feature/

Ok. Let's make sure that everything looks good by trying to make a

cd /usr/local/share/sendmail/cf

You'll see a nice, long (hopefully) configuration scroll by. If you don't see any errors, then send the output to our configuration file and copy to /etc/mail/:

m4 > /etc/mail/
cp /etc/mail/
touch /var/log/

Now, back in /etc/mail/, create the following files. Note that in every place here that you see "{at}", I mean "@", but I don't want email collection scripts to have these addresses:

cd /etc/mail
echo "GreetPause:localhost 	0" > access
touch domaintable
echo "" > generics-domains
echo "john	 	john {at}" > genericstable
echo "" > local-host-names
echo "" >> local-host-names

In your favorite editor, create virtusertable with the following:

john {at}		john
postmaster {at}		john
hostmaster {at}		john
webmaster {at}		john
abuse {at}		john
test {at}		john
{at}			error:nouser No user here by that name

Create the sasl file:

echo "pwcheck_method:saslauthd" > /usr/local/lib/sasl2/Sendmail.conf

Finally, make a quick script to update everything. I've called mine updatefiles:

makemap hash /etc/mail/access < /etc/mail/access
makemap hash /etc/mail/domaintable < /etc/mail/domaintable
makemap hash /etc/mail/genericstable < /etc/mail/genericstable
makemap hash /etc/mail/virtusertable < /etc/mail/virtusertable
/etc/rc.d/sendmail restart


chmod 700 updatefiles

The first time you run it, it should complain that sendmail isn't running, but then it should start it. Start smmsp, too:

/etc/rc.d/smmsp start

Technically, now you have a running email server! But it won't do very much until we configure DNS. Plus, you'll get warning messages in your mail logs until we configure DKIM and greylisting.

Stay tuned (who actually manually tunes a TV or radio these days?) for part 4!