I've been running my own mail server for close to 20 years... Through
years, I've gone from Interactive Unix (how many of you remember that one!) to
Red Hat Linux to Fedora Linux and now I'm moving to Ubunto (in
part thanks to the strong recommendations I've gotten from friends, especially Pat Patterson.
I host several services on my server and because we're at the end of a
relatively slow pipe, I use a dedicated server hosted at
Superb Hosting. I use a
dedicated server rather than the more typical web hosting or shared hosting
because it gives me better control over my services and because I host
a bunch of email domains for friends (some of which I simply forward to
their current ISP and some who actually get their mail from my system.
So, I needed to setup the following services on my server:
- DNS services for the 12 or so domans I manage (2 of my own and
the rest friends & family).
- Web server for my personal site.
- Email services for something like 12 domains as well.
Sounds simple, doesn't it?
Well, it wasn't that simple, but mostly becuase a) I was learning new
ways that things are done on Ubuntu vs Fedora, b) the tweaks of how I wnat
to do things typically involves manual configuration changes that aren't
always easily discerned from reading the man pages, and c) I like to
understand the why as well as the how when doing administrative stuff so
I spend a lot of time reading/reviewing/searching as I make my changes.
BTW - I'm not only willing, but actually want to do this so that I
can keep my hands a bit dirty (and maintain some basic understanding of
the current technologies used on servers). At work, they keep my grubby
little hands far away from the system adminstartion side of the houose.
Anyway, I thougt it would be useful to document what I went through as
I setup the server as it may help others trying to do the same things.
One note about the commands shown below: I logged in as root to do
the configuration, so you don't see "sudo (command)" for all
of the various commands. Some would say this might be a more dangerous
way to configure the system and I would agree for onsey twosey administrative
commands. However, for a long term session where you're doing nothing
other than administrative commands, sudo just gets in the way. And
yes, you need to be careful when you're logged in as root.
The following sections are presented below
OS Updates
First step with any new system is to ensure that I have the latest
and greatest software installed -- this is expecially important on an
internet visible server.
This involved running the following commands:
apt-get update # to update the apt-get configuration/data files
apt-get dist-upgrade # to upgrade all insalled packages to latest versions
This made sure I had the latest patches for this release of the OS.
However, I wanted also to make sure I had the latest OS version. For
Ubuntu, they have two development lines for servers: a somewhat frequently
changing/evolving line and a more stable Long Term Support (LTS) line.
Both lines get security patches regularly but LTS gets them for several years
longer while the fast changing line will more frequently require you to
upgrade to the latest OS version for patches.
Given what I do with the server, using the LTS line is the right thing
for me to do (which is the version that was installed by my provider). So
I ran the follwing commands to ensure I had the latest version:
apt-get install update-manager-core
do-release-upgrade
WHich reported that there was "No new release found" which is
correct as 8.04LTS is the latest LTS.
If, on the other hand, I wanted the latest OS rev (not just the latest
LTS OS rev), I could have edited the file:
/etc/update-manager/release-upgrades
and changed the line "Prompt=lts" to "Prompt=normal"
Miscellaneous Tools
As I went throught the isntallation and setup, I found a number of tools
were missing that I had to install to do the things I wanted to do, so I'll
list them here...
- System V Configuration files
I like to use the System V commands for managing the system (including the
service command to start/stop init.d services).
apt-get install sysvconfig
- Make
I use a lot of Makefiles for managing the build and installation of software
and packages. I was a bit suprised that my server didn't include that by
default, but I presume that was because it is a server and doesn't have the
development system installed either.
apt-get install make
Firewall
First thing to do is get the firewall up and running. While I plan
to tightly control which services are exposed on which ports, I still
feel much more comfortable having an explisit list of ports which are
accessible from the internet at large. I also like to setup and test
services locally while the are still blocked (including only opening up
access from my own systems so I can even do remote testing without worrying
about others getting into the server while it is a work-in-progress.
I use an iptables based firewall that is manually configured for the
system. I've been using pretty much the same setup for years though I
continuously tweak it. The script is written as an init.d service script
so that I can install it there and have it automatically run it at system
startup.
In addition to the typicall port protections, I also keep a blacklist
of IPs for which I block all access to my server. Systems get on this
list when I see that they are trying to hack into my system via repeated
SSH login attempts.
The core iptables rules in the script include:
#
# Create a new chain named "filter" and "OFilter"
#
iptables -N filter # add the new chain
#
# allow established connections
#
iptables -A filter -m state --state ESTABLISHED,RELATED -j ACCEPT
#
# if there are any ports to be dropped
#
if [ -f "${FILE_DroppedPorts}" ]; then
grep -v "^#" "${FILE_DroppedPorts}" | while read proto port
do
#
# for non-blank lines
#
if [ x${proto} != x ]; then
iptables -A filter -i eth0 -p ${proto} --dport ${port} -j DROP
fi
done
fi
#
# if there are any blocked IPs
#
if [ -f "${FILE_BlockedIPs}" ]; then
grep -v "^#" "${FILE_BlockedIPs}" | while read ip
do
if [ x${ip} != x ]; then
iptables -A filter -s ${ip} -j LOG
iptables -A filter -s ${ip} -j DROP
fi
done
fi
#
# allow ssh to this host from anywhere
#
iptables -A filter -p tcp --dport ssh -j ACCEPT
#
# allow HTTP/HTTPS to this host
#
iptables -A filter -i eth0 -p tcp --dport http -j ACCEPT
iptables -A filter -i eth0 -p tcp --dport https -j ACCEPT
#
# allow SMTP, SMTPS and SMTP/TLS to this host
#
iptables -A filter -i eth0 -p tcp --dport smtp -j ACCEPT
iptables -A filter -i eth0 -p tcp --dport smtps -j ACCEPT
iptables -A filter -i eth0 -p tcp --dport 587 -j ACCEPT
#
# allow IMAPs & POP3s to this host
#
iptables -A filter -i eth0 -p tcp --dport 993 -j ACCEPT
iptables -A filter -i eth0 -p tcp --dport 995 -j ACCEPT
#
# Allow DNS lookups to this host
#
iptables -A filter -i eth0 -p tcp --dport domain -j ACCEPT
iptables -A filter -i eth0 -p udp --dport domain -j ACCEPT
iptables -A filter -i eth0 \
-p udp --sport domain --dport 1024: -j ACCEPT
#
# allow outgoing ftp connections
#
iptables -A filter -p tcp --sport 21 \
-m state --state ESTABLISHED -j ACCEPT
iptables -A filter -p tcp --sport 20 \
-m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A filter -p tcp --sport 1024: --dport 1024: \
-m state --state ESTABLISHED -j ACCEPT
#
# let people ping us
#
iptables -A filter -p icmp -j ACCEPT
#
# Log all else
#
iptables -A filter -j LOG
#
# drop all else
#
iptables -A filter -j DROP
#
# install the input and output filters for input transactions
#
iptables -A INPUT -j filter
If you're interested, you can download the script and associated files
here.
Note that at this point, while I'm setting up the system, many of those
ports opened above are commented out and then, as I install the various
components (such as Apache2) I open the respective port.
Once completed, I installed the script in /etc/init.d using
the install directive in my Makefile (make
install) and then used the following command to setup the necessary
/etc/rc*.d files to ensure the firewall started as necessary when the system
was booted.
update-rc.d firewall defaults
Backup
Whether or not we actually do it, we all know that we should be
backing up our systems and data. This is especially true for remote systems
where we don't have easy local access.
My hosting provider does have backup options, but they cost re-occuring
money that I don't want to spend if I don't have to. So, my solution is to
backup my remote server onto one of my home servers (where I have TBs of
space anyway).
Since I have a firewall at home, I have to do the backup across a two
step ssh tunnel similar to what I decribed in
Backing up using S
SH & Rsync.
The first connection goes from my remote server to my firewall and the
second connection goes from the remote server through the first
connection to my backup server. I then rsync a number of directories
on the remote server to the backup sever including:
/etc, /var, /usr/local, /home
For security reasons, I require private key authentication for this
connection on both my gateway and my backup server, I use a user account
which has no login shell and no login directory and I configure that
the only service that can be accessed is the rsync service. Not perfect,
but it's good enough that I can get some sleep at night.
One problem with this setup is that the second ssh tunnel connects
to a port on localhost in order to establish the connection to the remote
system which can be a problem if there's other ssh connection tunnels
setup similarly. To get around that, I add an alias for my backup server
to the localhost entry in /etc/hosts file. So, rather than
connecting to localhost the second tunnel connects to the host
backup_server and thus keeps all of the SSH host keys separate.
If you're interested, you can download a modified (I removed any
credentials & system names) of the script from
here.
Bind9 (DNS Server)
I host DNS for a most of the domains for which I host mail (a few of
my friends host their own DNS, but use my mail server). A long time
ago, I wrote a shell script that creates the necessary configuration
files for the set of domains I manage (which makes it easy to add new
domains which are following the same rules and makes it easy to change
things around when I change my configuration).
Preparation for the move
Since nameserver changes can take some time to propogate through the
internet, this is the first service that I installed, configured and
exposed on the new system. In preparation for the move, I went to my
old nameserver and cranked down the caching settings for the domains
I hosted there in order to reduce the propagation time. My typical
settings are:
@ IN SOA mydomain.com. postmaster.mydomain.com. (
2010010200 ; serial number
86400 ; refresh every 24 hours
3600 ; retry after an hour
604800 ; expire after 7 days
86400 ; keep 24 hours
)
In preparation for the move, about a week in advance I
reduced these settings to:
@ IN SOA mydomain.com. postmaster.mydomain.com. (
2010010800 ; serial number
3600 ; refresh every hour
1800 ; retry after a half hour
7200 ; expire after 2 hours
3600 ; keep 1 hour
)
And finally, the day before the switch, I moved to:
@ IN SOA mydomain.com. postmaster.mydomain.com. (
2010010900 ; serial number
1800 ; refresh every half hours
1800 ; retry after a half hour
600 ; expire after 10 mins days
600 ; keep 10 mins
)
Installation and configuration
I installed the nameservice daemon software and utilities using:
apt-get install bind9 dnsutils bind9-doc resolvconf
I then copied my setup files from the old server to the new server. The
way that /etc/named.conf is managed has changed. On my old server
all of the settings were in that one file. However, in Ubuntu, that
file is intended to be unchanged and the local options are supposed to
be placed into /etc/named.conf.options while the host references
are intended to be placed into /etc/named.conf.local. So I changed
my scripts to match the new model and modified the Makefile to
correctly installe the new compoonents.
I've always run my named (the nameserice daemon) within a
chrooted environment and
every time I do this I have to yet again figure out what pieces need to
be there in order to get things working. So this time, I wrote a
CreateChroot.sh
script and ran it to create the chroot environment for me (and now I don't
have to figure it out from scratch the next time!). In addition to creating
the chroot environment, I had to change the OPTIONS directive in
/etc/default/bind to include "-t /var/cache/bind" so that the
file now looks like:
OPTIONS="-u bind -t /var/cache/bind"
#OPTIONS="-u bind"
# Set RESOLVCONF=no to not run resolvconf
RESOLVCONF=yes
In first setting up the new server, I made no changes other than to add
a new entry for my new server. So my new nameserver had pretty much the
same host entries that were on the old server. So I ran my script for
creating and installing my named configuration and restarted the bind9
service.
At this point, I opened the DNS TCP & UDP ports on my filewall so that
I could accept incoming nameservice requests. In order to test the service,
I went to my old server and used nslookup to test the new server:
# nslookup
> server newns.mydomain.com
Default server: newns.mydomain.com
Address: 192.168.169.11#53
> www.mydomain.com
Server: newns.mydomain.com
Address: 192.168.169.11#53
Name: www.mydomain.com
Address: 192.168.169.11
> mail.mydomain.com
Server: newns.mydomain.com
Address: 192.168.169.11#53
Name: mail.mydomain.com
Address: 192.168.169.11
>exit
This showed that things were working as I intended.
The Switchover
At this point, everything was ready to go, so I went to my domain
registry (Network Solutions)
and changed the host records for my nameservers to make the new nameserver
my primary dns server and my old server to be the secondary server.
This worked fine (though they warned me it could take 72 hours to
propagate) and I ran a bunch of tests from my home network, my work network
and my old server and everything was peachy keen.
Web Server
I run a web server for my own family web site. It's all hand-coded
html (yeah, kinda old fangled, but I haven't had the time, energy or
inclination to re-architect it. Setting it up on the new server was
pretty simple.
First step was to copy over the directory heirarchy from the old
server to the new server. Just tar'd it up and scp'd it over to
the new server and untar'd it within the /home/www directory.
Next step involved geting apache2 installed...
apt-get install apache2
The configuration for the web servers is located in:
/etc/apache2/sites-available
which comes with a single default file. I renamed this file
to be www.cahillfamily.com (allowing for more sites at some point
in the future) and editet that file to match up the settings from the old
server.
Server Side Includes (SSI)
SSI is a capability on the server which allows an html file to include
html from other files on the same web server. I use this feature extensively
to maintain a consistent menu structure by placing it in one file and
including it in all the html files on the server.
To enable this feature, I did the following:
- Set the Includes option within the configuration section for
my virtual host.
- Set the +XBitHack option as well. This allows me to indicate
to the server that there's an include directive in the file by simply
setting the executable bit on the file (rather than having to have a
particular suffix on the html file).
- Enabled mod-include by running the following command:
a2enmod include
Proxies
I run a few proxy severs on my remote server that I have found useful
when I'm behind some crazy firewalls or when an ISP has tight controls
on the number of outgoing connections -- I've run into racheted down
connection limits on my former home ISP
(RoadStar Internet and
at some hotels while on the road.
So I setup the proxies on my remot server, SSH to the server and
then tunnel my services through that server.
WARNING: You have to be very careful when you setup proxies
so that you don't end up creating an open proxy that others can use
to make it appear that bad things are coming from your server. If
you do set one up, do so carefully.
Socks 5 Proxy
Socks 5 is used for proxying many of my different Instant Messenger
connections (I have like 5 of them). For Ubuntu, the common/best one
seems to be the Dante-Server wich I installed using:
apt-get install dante-server
I configured it to only allow connections from the local system (since
I will have an SSH tunnel to the server). This prevents others from using
it unless they have internal access to my server.
*** /etc/danted.conf.orig 2009-12-31 11:29:41.000000000 -0500
--- /etc/danted.conf 2009-12-31 11:39:16.000000000 -0500
***************
*** 37,43 ****
# the server will log both via syslog, to stdout and to /var/log/lotsoflogs
#logoutput: syslog stdout /var/log/lotsoflogs
! logoutput: stderr
# The server will bind to the address 10.1.1.1, port 1080 and will only
# accept connections going to that address.
--- 37,43 ----
# the server will log both via syslog, to stdout and to /var/log/lotsoflogs
#logoutput: syslog stdout /var/log/lotsoflogs
! logoutput: syslog
# The server will bind to the address 10.1.1.1, port 1080 and will only
# accept connections going to that address.
***************
*** 45,54 ****
--- 45,58 ----
# Alternatively, the interface name can be used instead of the address.
#internal: eth0 port = 1080
+ internal: 127.0.0.1 port=1080
+
# all outgoing connections from the server will use the IP address
# 195.168.1.1
#external: 192.168.1.1
+ external: xx.yy.zzz.aaa
+
# list over acceptable methods, order of preference.
# A method not set here will never be selected.
#
***************
*** 57,66 ****
#
# methods for socks-rules.
! #method: username none #rfc931
# methods for client-rules.
! #clientmethod: none
#or if you want to allow rfc931 (ident) too
#method: username rfc931 none
--- 61,70 ----
#
# methods for socks-rules.
! method: username none #rfc931
# methods for client-rules.
! clientmethod: none
#or if you want to allow rfc931 (ident) too
#method: username rfc931 none
***************
*** 106,112 ****
# can be enabled using the "extension" keyword.
#
# enable the bind extension.
! #extension: bind
#
--- 110,116 ----
# can be enabled using the "extension" keyword.
#
# enable the bind extension.
! extension: bind
#
***************
*** 162,167 ****
--- 166,178 ----
# method: rfc931 # match all idented users that also are in passwordfile
#}
+ #
+ # Allow any connections from localhost (they will get here via SSH tunnels)
+ #
+ client pass {
+ from: 127.0.0.1/32 port 1-65535 to: 0.0.0.0/0
+ }
+
# This is identical to above, but allows clients without a rfc931 (ident)
# too. In practise this means the socksserver will try to get a rfc931
# reply first (the above rule), if that fails, it tries this rule.
Web (HTTP/HTTPS) Proxy Server
Since I already had the web server up and running, setting up a web
proxy was easy. First I had to ensure that the necessary modules were
installed and enabled:
a2enmod proxy
a2enmod proxy-connect
a2enmod proxy-ftp
a2enmod proxy-http
Then I edited the /etc/apache2/httpd.conf file and added the
following entries:
ProxyRequests On
<Proxy *>
AddDefaultCharset off
Order deny,allow
Deny from all
Allow from 127.0.0.1
</Proxy>
AllowConnect 443 563 8481 681 8081 8443 22 8080 8181 8180 8182 7002
The AllowConnect option is necessary if your're going to
proxy other connections (such as HTTPS). Most of those numbers are
legacy from some point in the past. The really necessary one is 443
(for HTTPS), some of the 8xxx ones were from when I was doing some
web services testing from behind a firewall at work (so I could invoke
the web service endpoint from my test application). Not sure
about all the others, but I'm not to worried about it since I only
accept proxy requests from the local system.
Mail Server
Setting up a mail server can be somewhat complex, especialy when you
throw in the fact that I was moving a running mail server to a new system
and adding new client capabilities.
On my old server, all of my users had to SSH into my server with private
key authetnication and then tunnel POP & SMTP over the SSH connection. This
could be a pain (to say the least) and restricted access for clients like
the iphone or other devices. Most of my users (family & friends) are using
an ssh tunnelling product from
VanDyke that was discontinued back
in 2004.
Installation
First step is to install the necessary components. Some of these were
already installed with the server OS package (e.g. Postfix) but there's
nothing wrong with making sure...
apt-get update
apt-get install postfix
apt-get install courier courier-pop-ssl courier-imap-ssl courier-doc
apt-get install spell mail
Before I start actually accepting and processing mail, I thought it best
to get the clients protocols all working, so onto the clients.
Mail Clients
I needed to enable support for your typical mail clients such as
Outlook
and Thunderbird
(which require IMAP or POP3 to retrive mail and SMTP to send mail) as
well as web browser clients. In the past, I have not supported web clients
and I have required mail clients to tunnel their POP3 & SMTP over ssh
tunnels. With the new server, I wanted to allow access without requiring
ssh tunnels so that other clients (such as my iPhone) that didn't have
ready support for ssh tunneling could get to the mail server. I also wanted
to add browser based support so that people could check their email from
other locations (such as a friends computer).
This involved the following steps:
Secure Sockets Layer (SSL)
For remote access to my server I needed to enable SSL
so that user credentials were protected. My intent was to enable SSL on
all the standard mail client protocols (SMTP, IMAP and POP) and to enable
browser based access to mail via HTTPS and a web server based mail client.
Certificate Generation
In order to support SSL, I needed to get an SSL certificate. I could
have created my own certificate and signed it myself, but that would have
lead to error messages from the clients telling my users that perhaps they
shoudln't trust my server. Instead, I signed up for an SSL certificate
from
GoDaddy which was running a
special for $12.95/year for up to 5 years.
In order to create the certificate, I had to generate my private key
and then a certificate signing request using the following commands:
*** make sure openssl is installed
# apt-get install openssl
*** Generate 4096 bit RSA server key
# openssl genrsa -des3 -out server.key 4096
Generating RSA private key, 4096 bit long modulus
.............................................................................++
................................................................................
......................................++
e is 65537 (0x10001)
Enter pass phrase for server.key: abcd
Verifying - Enter pass phrase for server.key: abcd
*** Generate certificate signing request for server key (note that the
*** "Common Name" must be the name of the host that the clients will connect
*** to if you don't want to get ssl errors)
# openssl req -new -key server.key -out server.csr
Enter pass phrase for server.key: abcd
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]: Virginia
Locality Name (eg, city) []: Waterford
Organization Name (eg, company) [Internet Widgits Pty Ltd]: Cahills
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []: mail.cahillfamily.com
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
At this point, I took the server signing request, server.csr,
and sent it to GoDaddy to get them to sign it and create my certificate.
If, on the other hand, I wanted to do a self-signed certificate, I would
have performed the following steps:
*** sign the csr using our own server key (making this a self-signed cert)
# openssl x509 -req -days 1825 -in server.csr \
-signkey server.key -out server.crt
Signature ok
subject=/C=US/ST=Virginia/L=Waterford/O=Cahills/CN=mail.cahillfamily.com
Getting Private key
Enter pass phrase for server.key: abcd
To test this, I configured Apache2 to support SSL and tested
access to https://mail.cahillfamily.com. I first needed to enable the
SSL module using the following command:
a2enmod ssl
I took the server key and server certificate and place them into
a secure non-standard location (no need to advertise where) and set the
access modes on the directory to restrict it to root only. In
order for the server key to be used without a pass phrase, I ran the
following commands to remove the pass phrase from the file:
mv server.key server.key.safe
openssl rsa -in server.key.safe -out server.key
I copied the default Apache2 site file into one
for mail.cahillfamily.com and set it up using the following
commands:
cp /etc/apache2/sites-available/default /etc/apache2/sites-available/mail.cahillfamily.com
ln -s /etc/apache2/sites-available/mail.cahillfamily.com /etc/apache2/sites-enabled/mail.cahillfamil
y.com
I then edited the configuration file to enable SSL and to point to
the newly installed certificate and key files:
NameVirtualHost *:443
<VirtualHost *:443>
ServerAdmin webmaster
ServerName mail.cahillfamily.com
DocumentRoot /home/www/mail.cahillfamily.com
ErrorLog /var/log/apache2/error.log
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
CustomLog /var/log/apache2/access.log combined
ServerSignature On
SSLEngine On
SSLCertificateFile /path-to-ssl-files/server.crt
SSLCertificateKeyFile /path-to-ssl-files/server.key
</VirtualHost>
I also wanted to automatically redirect any http: access to
mail.cahillfamily.com to https: access, so I added the following
section to the default site file which uses the RedirectPermanent
directive to automatically redirect access on port 80:
<VirtualHost *:80>
ServerAdmin webmaster
ServerName mail.cahillfamily.com
RedirectPermanent / https://mail.cahillfamily.com
</VirtualHost>
IMAP and POP
After poking about some, I came to the conclusion that the right mail
server for me to use to expose IMAP and POP interfaces for my mail clients
is the Courier Mail Server.
Courier requires that you use the MailDir structure for user
mailboxes while Postfix uses the mbox structure by default. So I
changed Postfix to use the MailDir structure by adding the following
setting to /etc/postfix/main.cf:
home_mailbox = Maildir/
I manually created an empty Maildir structure for all my user
accounts.
For SSL, Courier requires the key and the certificate to be in a
single .pem file. So I concatenated server.key and server.crt
into a single server.pem file.
I edited the /etc/courier/imapd-ssl file to make the following
changes:
- Set SSLPort to 993.
- Set both IMAPDSSLSTART and IMAPDSTARTTLS options to
YES to allow both IMAP over SSL and TLS within IMAP (the latter
being a TLS session that's started from within the IMAP session while
the former is a plain IMAP session over an SSL tunnel).
- Set IMAP_TLS_REQUIRED to 0 so that local connections from the
web mail server could make use of imap without having to do TLS on
the local (same system) connection. I planned to still block the
standard IMAP port (143) in the firewall, so remote clients would not
be able to access their mail without SSL/TLS).
- Set TLS_CERTFILE to point to the recently created server.pem
file.
I edited the /etc/courier/imapd file to make the following
changes:
- Added "AUTH=PLAIN" to the IMAP_CAPABILITY setting so that
plain text authentication is allowed on non-tls connections to the imap
server. This is necessary for the local connection from some web server
mail clients which don't come with support for CRAM-MD5 or other non-PLAIN
authentication mechanisms.
I edited the /etc/courier/pop3d-ssl file to make the following
changes:
- Set SSLPort to 995.
- Set both POP3DSSLSTART and POP3DSTARTTLS options to
YES to allow both POP3 over SSL and TLS within POP3 (the latter
being a TLS session that's started from within the POP3 session while
the former is a plain POP3 session over an SSL tunnel).
- Set POP3_TLS_REQUIRED to 0 so that local connections from the
web mail server could make use of imap without having to do TLS on
the local (same system) connection. I planned to still block the
standard POP3 port (110) in the firewall, so remote clients would not
be able to access their mail without SSL/TLS). However, this would
enable my existing clients which ssh to the server and then use
non-TLS POP to still be able to get their email.
- Set TLS_CERTFILE to point to the recently created server.pem
file.
Restarted the courier related services:
service courier-imap stop
service courier-imap-ssl stop
service courier-pop stop
service courier-pop-ssl stop
service courier-imap start
service courier-imap-ssl start
service courier-pop start
service courier-pop-ssl start
Yeah, I probably could have simply used the "restart" command on each of
them but I wanted to have them all stopped and then start them all so I was
sure that they call came up cleanly under the same configuration.
Now it was time to test things. First a quick telnet connection to
the local imap port (143):
# telnet server 143
* OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THRE
AD=REFERENCES AUTH=PLAIN SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Cop
yright 1998-2005 Double Precision, Inc. See COPYING for distribution informatio
n.
01 LOGIN username password
01 OK LOGIN Ok.
0000 logout
* BYE Courier-IMAP server shutting down
0000 OK LOGOUT completed
closed
So that worked. I ran a similar test for POP3 which also worked. Now
I was ready for some remote testing. First step was to go back to my
firewall and open ports 993 (IMAPS) and 995 (POP3S)
to allow incomming connections to the IMAP and POP services.
Then I went to http://www.wormly.com/test_pop3_mail_server and ran
several tests with the POP3S implementation (with test accounts, of course)
which all worked fine.
I didn't see a similar testing tool for IMAP, so I ran some tests from one
of my home computers using the following command:
openssl s_client -crlf -connect mail.cahillfamily.com:993
Which worked like a charm (with some finagling with the /etc/hosts
file to override mail.cahillfamily.com's IP address). This also
worked like a charm, so at this point I figured I had IMAP and POP
up and running.
Authenticated SMTP
When setting up an SMTP server, you have to be very careful that you don't
configure your server as an open relay (where it will send mail from
anyone to anyone). It seems that hackers, scammers and spammers are
forever looking for new open relays that they can use to send out spam and
shortly after opening an SMTP port on the internet you can usually find
attempts to make use of the server as a relay.
For basic unauthenticated SMTP (e.g. where there's no local user
authentication within the SMTP session), I configured the server to only
accept incomming mail whose delivery address is within one of my managed
domains. Any mail with a destination address outside of my domain is
rejected before we accept the mail message itself.
However, that configuration wouldn't work very well for my users who
typically do want to send mail to people outside of my domain. In the
past, my solution was simple: ssh tunnel to my host then sent mail via
SMTP on the local host interface where I could treat any local connections
as, by default, authenticated.
While I am continuing to allow that configuration with the new server
setup, it wouldn't work for those users trying to use a mail client without
the ssh tunnel. So I had to enable authenticated SMTP and I had to
configure it to require such sessions over SSL.
The SMTP server is managed by Postfix itself. So first step was to
modify the /etc/postfix/main.cf configuration file to
only accept main with recipients in my networks:
#
# restrict smtp operations on unauthenticated (port 25) connections
#
smtpd_recipient_restrictions = permit_mynetworks,reject_unauth_destination
Then I modified the /etc/postfix/master.cf configuration file
to enable both TLS within SMTP sessions and SMTP over SSL/TLS by including
the following directives:
submission inet n - - - - smtpd
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
smtps inet n - - - - smtpd
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
These settings, along with the base configuration, should give me
server to server SMTP on port 25 and client to server user authenticated
SMTP over TLS/SSL on ports 465 and 587.
Now that I have SMTP which allows for authentication, I had to install
and configure the sasl authentication daemon as follows:
- I installed the package using:
apt-get install libsasl2 sasl2-bin
-
I edited the /etc/defaults/saslauthd to make the
following changes:
- Set START=yes so the daemon will start.
- Configured saslauthd to place it's runtime information underneath the
postfix chroot environment by changing the OPTION parameter and
adding the following lines:
PWDIR="/var/spool/postfix/var/run/saslauthd"
OPTIONS="-c -m ${PWDIR}"
PIDFILE="${PWDIR}/saslauthd.pid"
- I created the saslauthd run directory using:
mkdir -p /var/spool/postfix/var/run/saslauthd
- Configured saslauthd to leave its files readable by postfix (so
postfix could communicate with the daemon) using the following command:
dpkg-statoverride --force --update --add root sasl 755 \
/var/spool/postfix/var/run/saslauthd
- Created /etc/postfix/sasl/smtpd.conf file and added the
following lines:
pwcheck_method: saslauthd
mech_list: plain login
- Restarted both saslauthd and postfix
Now I was ready to start testing, so I went back to my
firewall and opened ports 25 (SMTP), 465 (SMTP over
SSL) and 587 (TLS within SMTP) so that I could start testing.
To test all of this you could use a mail client, or if you're a bit
more adventurous (and want to see exactly what's going on) you can do
this manually within a telnet/openssl connection). The following is
an example test session:
$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.cahillfamily.com ESMTP Postfix (Ubuntu)
ehlo mail.cahillfamily.com
250-mail.cahillfamily.com
250-PIPELINING
250-SIZE 10240000
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
mail from: user@localhost
250 2.1.0 Ok
rcpt to: someuser@someotherhost.com
250 2.1.5 Ok
data
354 End data with .
Subject: Test message
Sending yet another test... hope it gets there...
.
250 2.0.0 Ok: queued as B28C461A0A9
quit
221 2.0.0 Bye
Connection closed by foreign host.
That's a standard, unauthenticated SMTP session. I find using the
manual sesssion for testing makes it easier to identify what the problem
is when there is a problem. For example, in the list of responses after
my "ehlo" command, you see "250=-STARTTLS" - this indicates that TLS is
enabled within the server).
To test an authenticated SMTP session, you will need to enter a
command similar to the following (I usually do this right after the
"ehlo" command, though I'm not sure if it has to be exactly there):
auth plain AHVzZXJpZABwYXNzd29yZA==
235 2.7.0 Authentication successful
The "AHVzZXJpZABwYXNzd29yZA==" parameter is a base64 encoding of a
plain text SASL authentication string. You can generate one manually
using the following perl command:
perl -MMIME::Base64 -e 'print encode_base64("\000userid\000password")'
Where userid = the test user's id and password = the test user's password
If you have a special character in either string (such as an @ in the
user id (e.g. user@host) you need to escape the character (e.g. "\@").
So, now that I have all that, I ran the following tests:
Web Server Mail Client
For browser clients, there are a couple of obvious possibilities
that come to mind:
I took a look at the two tools and decided to go with Squirrel Mail
and, for now, just install the basep toolkit. I'll explore the Outlook
model at some point in the future. Ubuntu has SquirrelMail available
as a standard package so I installed it using the following command:
apt-get install squirrelmail
I then modified the /etc/apache2/sites-available/mail.cahillfamily.com
configuration file to use the squirrelmail application as the document
root, so my users go straight into the application when they visit
mail.cahillfamily.com in a browser. The modified file looks as follows:
NameVirtualHost *:443
<VirtualHost *:443%gt;
ServerAdmin webmaster@localhost
ServerName mail.cahillfamily.com
DocumentRoot /usr/share/squirrelmail
ErrorLog /var/log/apache2/error.log
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
LogLevel warn
CustomLog /var/log/apache2/access.log combined
ServerSignature On
SSLEngine On
SSLCertificateFile /etc/ssl/server.crt
SSLCertificateKeyFile /etc/ssl/server.key
Include /etc/squirrelmail/apache.conf
</VirtualHost%gt;
Used by browser to test it and everything seems kosher.
SPAM filtering
To filter or not to filter.... For many years I ran my server with
no server-side filtering and instead relied on client filtering. However,
the abundance of crap that keeps on coming only seems to grow exponentially
every year and I finally convinced myself that not only was server side
filtering necessary, but it was mandatory. This is especially evident when
you're trying to download mail after having been disconnected for a day or
so and find that you have hundreds of email messages, most of which are
clearly SPAM.
I use spamassassin for spam filtering. Looking around at most of the
how-to's/docs I see that most people recommend usiing spamassassin to just
flag spam, but then go ahead and deliver it to the user's mailbox. This
is probably the best solution if you don't want to lose any potential
emails that have incorrectly been marked as SPAM. However, that means
that my clients have to download hundreds of spam messages just to throw
them out when the got to the client.
For my system, I'd rather have Spamassassin get rid of at least some
spam and then let some of the questionalbe stuff through. So, I've setup
things such that mail messages that get a Spamassassin grade of 10 or higher
get saved off into a directory on the server (one directory for each day
to ease management). For messages that have a grade between 5 and 10, the
subject gets re-written to include a SPAM indicator, but the message
is still delivered to the intended recipient.
I've been doing it this way for the past 2 years. We get on the order of
five thousand (yeah: 5,000) messages culled this way each day and I've yet
to find or get a report of any false positives. Note that there's still
a bunch of email that gets through with grades between 5 and 10.
Anyway, to set this up on the new server:
- Install the latest version of spamassassin using:
apt-get update
apt-get install spamassassin spamd
- Installed spamchk script (not sure where I originally got it, but I've been using it on my old mail server for several years now) in
/usr/local/bin/spamchk
-
Created /var/spool/postfix/spam/save and /var/spool/postfix/spam/tmp
directories for processed messages
-
Edited the /etc/postfix/master.cf file to add an output filter for mail
coming in the default smtp connection (we don't need it on the SSl
connections since they are authenticated) and to add the spamck
invocation. Modified lines look as follows:
smtp inet n - - - - smtpd
-o content_filter=spamchk:dummy
And at the end of the file, added:
#
# SpamAssassin check filter
#
spamchk unix - n n - 10 pipe
flags=Rq user=spamd argv=/usr/local/bin/spamchk -f ${sender} -- ${recipient}
-
By default, Spamassassin places a detailed spam report in any message
that is flagged as spam (spam score >= 5) and moves the original message
to an attachement. I find this cumbersome and so instead I like to
flag the subject of the message with a "[SPAM]" flag and otherwise leave
the message alone (you do still get the Spamassassin headers added to the
message, but they are hidden from the default view in most mailers).
To achieve this, I edited the
/etc/mail/spamassassin/local.cf file and make the following
changes:
*** local.cf.orig 2010-01-02 10:45:58.000000000 -0500
--- local.cf 2010-01-09 20:54:46.000000000 -0500
***************
*** 9,21 ****
# Add *****SPAM***** to the Subject header of spam e-mails
#
! # rewrite_header Subject *****SPAM*****
# Save spam messages as a message/rfc822 MIME attachment instead of
# modifying the original message (0: off, 2: use text/plain instead)
#
! # report_safe 1
# Set which networks or hosts are considered 'trusted' by your mail
--- 9,21 ----
# Add *****SPAM***** to the Subject header of spam e-mails
#
! rewrite_header Subject [SPAM]
# Save spam messages as a message/rfc822 MIME attachment instead of
# modifying the original message (0: off, 2: use text/plain instead)
#
! report_safe 0
# Set which networks or hosts are considered 'trusted' by your mail
- Spamassassin likes to learn about its mistakes (both positive and
negative). Since my users don't have local access to the system, I need
to add aliases which allow people to forward mail attachments that are
or are not spam so that Spamassassin can use that information in its
learnings.
First step was to get the sa-wrapper.pl script from
Stefan Jakobs. This
script had a dependency on the perl modlue MIME::Tools which I
used the following comand to download and install it (as well as a
bunch of dependencies it had):
cpan -i MIME::Tools
Then I setup the aliases in /etc/aliases as follows:
# Spam training aliases
spam: "|/usr/local/bin/sal-wrapper.pl -L spam"
ham: "|/usr/local/bin/sal-wrapper.pl -L ham"
When I tested it, the script failed because it couldn't open/write to
the log file. I manually created the log file and set it be writable
by the tool.
The Switchover
The switchover had to be handled carefully in an attempt to not loose
any mail as I moved things (or as little as possible). The sequence I
worked out and used was as follows:
- Stop mail services on both the old and the new servers -- ALL
mail services: SMTP, POP3, IMAP, etc.
- On the old server, tar up all of the existing user accounts and user
mailboxes and transfer them to the new server.
- Copy the /etc/passwd and /etc/shadow files to the new
server and copy out the user accounts that are moving and add them to the
existing /etc/passwd and /etc/shadow files on the new server.
- Copy the /etc/postfix configuration files from the old server to
the new server and merge in any of the local settings from the old server. In
particular the virtual domains information for all of the domains I host had
to be incorporated into the new setup.
- Copy the /etc/aliases file from the old server to the new server
editing the file to remove any extraneous/old/useless entries. Run
newaliases to notify Postfix of the changes.
- Untar the user accounts in /home on the new server and set the
owner/group ownership as necessary.
- Convert Mbox mailboxes to the new Maildir format on the new server.
While I do alot of relaying of mail, there are a number of people who
actually get their mail off of my server and so I needed to move their
incomming mail to the new server and beccause we changed from mbox format
to Maildir format, I needed to split the mail up in to individual files.
I found a perl script to do the conversion (mb2md) which
I downloaded from
here. Ran
a few tests and figured out that I would use the command as follows:
mb2md -s "full path to mbox file" -d "full path to Maildir directory"
And, since I was doing this as root, I would need to:
chown -R user.group "full path to Maildir directory"
so that the right user owned all the files.
- Create Maildir structures for those users who didn't have mail in their
mailboxes.
For those users who didn't have mail sitting in their mbox files on the old
system, I would need to create the correct heirarchy within their login
directory for Maildir delivery. So I ran a script similar to the following
(I just did it from the command line, so I don't have an actual copy of the
script) in /home:
for user in user_list
do
mkdir $user/Maildir $user/Maildir/cur $user/Maildir/new $user/curdir/tmp
chown -R $user $user/Maildir
done
- On both servers: Edit the DNS records to change the IP
address for mail.cahillfamily.com to be the new server and assign the name
oldmail.cahillfamily.com to the old server. And, of course, pubish
these changes.
- Enable mail services on the new server (do not, for at least a day or
so, enable mail services on the old server in order to force any mail in
other SMTP queues to go to the new server).
- Test the setup by sending emails to various users in my hosted domains
from local clients, clients in my hame and from my work email account to
ensure that the changes had propogated out to the real world.
Epilogue
That's about it... At least what I remember. I'm sure that there are
things I did during the move that I forgot to write down, but I did try to
record everything. I'll update this if/when I figure out anything I did
wrong or forgot to note.
I hope someone out there finds this useful. I know I will the next time
I need to move the mail server to a new system.
Tags :
ubuntu
/ postfix
/ firewall
/ spamassassin
/ apache2
/ ssl