Mail server setup guide

From NickWiki

Jump to: navigation, search

Contents

Purpose

There are plenty of guides to setting up robust mail servers on the interblag, but they all seem to get things just a bit wrong, or have features that I'd like to use in a different type of setup. For a while now, I've had a functional server cobbled together, but now that I'm relocating my server I have a chance to go with a slightly more stable approach.

After a failed initial attempt to synthesize an ideal setup guide from the several that I've used in the past, I've decided to document my steps here for two reasons. The first is so that I can keep a record of the steps I've taken and backtrack whenever I mess something up. The second is that that list of steps can then be compiled into a half-decent guide that (in my opinion, at least,) does things "right" and that I can use in the future.

So for now this will be kind of a scratch space for my work, but over time it should congeal into something more coherent and useful.

Overview

I will be setting up a Postfix-based mail server that stores the information for multiple domains and users in a MySQL database. Courier [soon to be Dovecot] will be used for IMAP and perhaps POP, although I'm not sure if I want to have support for the latter. Connections will be encrypted through SSL and STARTTLS, and secure authentication will be done through Cyrus SASL. There will be a webmail interface available (currently SquirrelMail, but may change to something slightly nicer to use). Mail will be stored in vmail directories based on domain and username to allow for sensible organization of users.

This guide is heavily based on those found at Flurdy and slicehost, preferring mostly to follow Flurdy, but with regard to directory structure and SASL, along with a few other things, it follows the slicehost guide.

Notes

Many commands will require you to elevate your privileges to root. I have made an attempt to show root prompts (#) where root is necessary, and regular prompts ($) where it is not, but just use common sense if something isn't working.

This guide is written for a Debian-based system (specifically Ubuntu 8.04 LTS), but aside from the use of aptitude and some folder locations, should work on other distros.

Preliminary Setup

  • Make sure SSH is set up so that you can remotely log into your server. This is usually as easy as doing something like
# aptitude install openssh-server
  • You should have a non-privileged user on your system so that you're not running as root all the time.
  • It's probably a good idea to make sure you have added the restricted, universe, and multiverse repositories to your apt sources.list.
  • Make sure the hostname is correctly set on the server:
# vim /etc/hostname
  • Replace the hostname with the server's hostname. I'll be using www.pieps.org, since this is also a web server.
  • Open /etc/mailname and set that to the name of your mail server (I'm using mail.pieps.org):
# vim /etc/mailname
  • Restart to make things get set properly.

<todo> make sure RDNS works with the above - may need to set hostname so that it matches the RDNS entry.


Primary Installation

We're going to take the Flurdy approach here, and just install a lot of stuff en masse. Later, we will configure the main install, then install some straggling stuff on top.

  • First off, we'll install MySQL:
    # aptitude install mysql-server
    • The MySQL package will ask you for a root password for your database. Make sure you remember that password! It's a pain in the ass to change if you forget it. I'll refer to my password as MysqlRootPasswd.
  • Next we'll install Postfix:
    # aptitude install postfix postfix-mysql postfix-tls
    • It will ask you what type of mail server you're running. Select internet site.
    • Since you've already set /etc/mailname, the default mail hostname value that it asks you for should work just fine.
  • Now we'll install SASL and related software:
    # aptitude install libsasl2-2 libsasl2-modules-sql sasl2-bin libauthen-sasl-cyrus-perl libpam-mysql
  • Next up is Courier:
    # aptitude install courier-imap courier-imap-ssl courier-ssl courier-authlib-mysql courier-authdaemon
    • Answer No to the question about web-based administration
    • Courier will warn you about your SSL certificate location. We'll deal with that in a bit.
  • Next up is the firewall:
    # aptitude install shorewall shorewall-doc
  • Finally, we'll install openssl (for certificate signing):
    # aptitude install openssl

Primary Configuration

Now we'll start configuring things.

Shorewall Part 1

It's important to get this set up now so that we have a secure server from the get-go. We'll start off only allowing SSH traffic, then once things are working we'll open up the services we need.

  • Default values for Shorewall are in /usr/share/doc/shorewall-common/default-config.
  • Examples are in /usr/share/doc/shorewall-common/examples.
  • First we configure which network adapters will access the net:
    # cp /usr/share/doc/shorewall-common/default-config/interfaces /etc/shorewall/.
    # vim /etc/shorewall/interfaces
    Add the following line (assuming your interface is named eth0):
    net	eth0		detect		dhcp,tcpflags,logmartians,nosmurfs
  • Next we configure network zones:
    # cp /usr/share/doc/shorewall-common/default-config/zones /etc/shorewall/.
    # vim /etc/shorewall/zones
    Add the firewall if not there and the internet as a zone:
    fw	firewall
    # loc	ipv4
    net	ipv4
  • Since this guide does not deal with multiple zones going through a single interface, a hosts file is not necessary
  • Next we set the default firewall access policy:
    # cp /usr/share/doc/shorewall-common/default-config/policy /etc/shorewall/.
    # vim /etc/shorewall/policy
    $FW		net		ACCEPT
    net		$FW		DROP		info
    net		all		DROP		info
    # THE FOLLOWING POLICY MUST BE LAST
    all		all		REJECT		info
  • For safety in case it goes down:
    # cp /usr/share/doc/shorewall-common/default-config/routestopped /etc/shorewall/.
    # vim /etc/shorewall/routestopped
    eth0		-			routeback
    You may put in a netmask of your ip range if you are more concerned.
  • Next, we add the main firewall rules. There are predefined macro rules in /usr/share/shorewall. Their usage is explained here. For now, we will allow SSH traffic:
    # cp /usr/share/doc/shorewall-common/default-config/rules /etc/shorewall/.
    # vim /etc/shorewall/rules
    SSH/ACCEPT	net		$FW
  • Shorewall allows you to check your rules for syntax consistency:
    # shorewall check
  • If all is well, change /etc/default/shorewall so that the line towards the top reads
    startup=1
    . This allows for shorewall to be started/stopped.
  • You may want to set the ADMINISABSENTMINDED variable to Yes in your /etc/shorewall/shorewall.conf file. This will keep your firewall from terminating existing connections when you restart it.
  • Restart the firewall:
    # /etc/init.d/shorewall restart
    DO NOT TERMINATE YOUR SESSION UNTIL YOU ARE SURE SHOREWALL WORKS
  • Now open a new terminal and try to ping your server.
    $ ping mail.pieps.org
    Your packets should all be dropped.
  • Next, try SSHing into your server from that terminal.
    • If all has gone well, you should be able to access it.
    • If not, something has gone wrong. In your existing session on your server (that you didn't terminate, right?), set the line in /etc/default/shorewall back to startup=0 so that you're not screwed if you restart your server, and try to figure out the problem. Once everything seems to be ok again, reset startup=1, restart shorewall, and try pinging/SSHing again.
  • We will deal with further firewall configuration in a bit.

Postfix

Main config

  • Open the Postfix config file /etc/postfix/main.cf
    • Ubuntu puts some decent default values here, but we may override some of them.
    • /etc/mailname already contains your mail server's hostname, but we'll specify the name of the server in the config file:
      myhostname = mail.pieps.org
    • We also need to set the origin (the part that comes after the @) properly:
      # myorigin = /etc/mailname
      myorigin = pieps.org
    • Decide what the greeting text will be for other mail servers. As Flurdy says, "enough info so it is useful, but do not divulge everything to potential hackers."
      smtpd_banner = $myhostname ESMTP $mail_name
    • Flurdy goes into a bit of detail about how to relay mail through your ISP's SMTP server, but I'm doing everything myself and need no relay hosts:
      # leave blank to send mail yourself
      relayhost =
      # or use an accessible SMTP server
      relayhost = smtp.yourisp.com
    • We want to accept connections from anywhere (the internet or locally):
      inet_interfaces = all
    • We only want to trust ourselves for relay, access control, etc.:
      mynetworks_style = host
      Also, make sure to comment out any mynetworks entries you see, as they'll clobber the default specified by mynetworks_style:
      # mynetworks = 127..0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
    • Domain Masquerading: <TODO> - not sure if I'll need to do this as long as myorigin is set correctly
    • We're going to be using virtual domains, so the following need to be empty:
      local_recipient_maps =
      mydestination=
      <NOTE> The Postfix documentation here expressly says not to do this since it allows spammers to backscatter folks due to the fact that the server will accept the mail, then reply that the mail is undeliverable. This may be ok in this case, since we're going to be mapping all desired local addresses through aliases, and all other addresses will be rejected for various reasons (including the fact that mydestination is set to null, making no addresses local as far as Postfix is concerned, I think). This is a good thing to think about changing if I ever want local mail delivered automatically. As it is, however, I'd rather have local mail (sent to localhost) map to specific addresses through aliases.
      <UPDATE> after a bit of research, it seems that incomplete addresses are automatically rewritten by trivial-rewrite to have @$myorigin at the end, then delivered accordingly. So that's sorted. Meanwhile, the old Flurdy-based setup I had before has all @localhost aliased at root@localhost. So this may produce some strange behavior based on whether programs send mail with user@localhost or just user, but at least I now know how this mess works. Anyway, it should be safe to keep local_recipient_maps null as long as mydestination is as well, since then no mail will be treated as local.
    • Now for a few numbers determining lifetimes, etc. (directly taken from Flurdy):
      # how long if undelivered before sending warning update to sender
      delay_warning_time = 4h
      # will it be a permanent error or temporary
      unknown_local_recipient_reject_code = 450
      # how long to keep message on queue before return as failed.
      # some have 3 days, I have 16 days as I am backup server for some people
      # whom go on holiday with their server switched off.
      maximal_queue_lifetime = 7d
      # max and min time in seconds between retries if connection failed
      minimal_backoff_time = 1000s
      maximal_backoff_time = 8000s
      # how long to wait when servers connect before receiving rest of data
      smtp_helo_timeout = 60s
      # how many address can be used in one message.
      # effective stopper to mass spammers, accidental copy in whole address list
      # but may restrict intentional mail shots.
      smtpd_recipient_limit = 16
      # how many error before back off.
      smtpd_soft_error_limit = 3
      # how many max errors before blocking it.
      smtpd_hard_error_limit = 12
    • Now some restrictions. Be careful that each setting is on one line (also from Flurdy):
      # Requirements for the HELO statement
      smtpd_helo_restrictions = permit_mynetworks, warn_if_reject reject_non_fqdn_hostname, reject_invalid_hostname, permit
      # Requirements for the sender details
      smtpd_sender_restrictions = permit_mynetworks, warn_if_reject reject_non_fqdn_sender, reject_unknown_sender_domain, reject_unauth_pipelining, permit
      # Requirements for the connecting server
      # I'm not a fan of RBLs, so I'm leaving these out
      # smtpd_client_restrictions = reject_rbl_client sbl.spamhaus.org, reject_rbl_client blackholes.easynet.nl, reject_rbl_client dnsbl.njabl.org
      # Requirement for the recipient address
      smtpd_recipient_restrictions = reject_unauth_pipelining, permit_mynetworks, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, permit smtpd_data_restrictions = reject_unauth_pipelining
    • More restrictions:
      # require proper helo at connection
      smtpd_helo_required = yes
      # waste spammers' time before rejecting them
      smtpd_delay_reject = yes
      disable_vrfy_command = yes
    • Now some maps and lookups for our virtual domains. This is a combination of Flurdy's approach and Slicehost's. Overwrite any that already exist:
      # this specifies where the virtual mailbox folders will be located
      virtual_mailbox_base = /var/spool/mail/virtual
      # this is for the mailbox location for each user
      virtual_mailbox_maps = mysql:/etc/postfix/mysql_mailbox.cf
      # and their user id (virtual)
      # virtual_uid_maps = mysql:/etc/postfix/mysql_uid.cf
      virtual_uid_maps = static:5000
      # and group id (virtual)
      # virtual_gid_maps = mysql:/etc/postfix/mysql_gid.cf
      virtual_gid_maps = static:5000
      # and this is for aliases
      virtual_alias_maps = mysql:/etc/postfix/mysql_alias.cf, mysql:/etc/postfix/mysql_email.cf
      # and this is for domain lookups
      virtual_mailbox_domains = mysql:/etc/postfix/mysql_domains.cf
      # this is how to connect to the domains (all virtual, but the option is there)
      # not used yet
      # transport_maps = mysql:/etc/postfix/mysql_transport.cf
  • We need to make sure that the aliases file points to sane addresses. Most importantly, mail to root should go somewhere sensible. Then again, since local delivery should be disabled (we have no local domains), this may be unnecessary. Do however make sure that local usernames are set up correctly in the virtual aliases table later on.
  • Now we need to set up the folder where all the virtual mail will be stored. This may have been done for you by the postfix install. If you're not sure, check your /etc/passwd file for a user called virtual:
    # mkdir /var/spool/mail/virtual
    # groupadd virtual -g 5000
    # useradd virtual -u 5000 -g 5000
    # chown -R virtual:virtual /var/spool/mail/virtual

Postfix MySQL config

  • Now we need to set up the files that dictate how to look up information in the database. This is going to use information that we have not yet added to the MySQL database. That will be covered in the following section.
    • Edit/create the user mailbox location config file (taken from Slicehost):
      # vim /etc/postfix/mysql_mailbox.cf
      user=mail
      password=MyMailPassword
      dbname=maildb
      query = SELECT CONCAT(SUBSTRING_INDEX(email,'@',-1),'/',SUBSTRING_INDEX(email,'@',1),'/') FROM users WHERE email='%s' and enabled = 1
      hosts = 127.0.0.1
      This file is tricky - it compiles the directory structure first from the domain of the address (the part after the @), then from the user name (the part before the @). This is nice if you want maildirs to be automatically created for users and not have to worry about name collisions. It's just as easy and more flexible to specify all this by hand (for instance, Flurdy just has a field in the database for the dir that can be populated any way the admin likes), but I really like that this automates the whole process.
    • Edit/create the alias config file:
      # vim /etc/postfix/mysql_alias.cf
      user=mail
      password=MyMailPassword
      dbname=maildb
      table=aliases
      select_field=destination
      where_field=email
      hosts=127.0.0.1
      additional_conditions = and enabled = 1
    • Edit/create the email config file (this keeps us from having to add entries to aliases that map from an address to itself):
      # vim /etc/postfix/mysql_email.cf
      user=mail
      password=MyMailPasssword
      dbname=maildb
      table=users
      select_field=email
      where_field=email
      hosts=127.0.0.1
      additional_conditions = and enabled = 1
    • Edit/create the domain details config file:
      # vim /etc/postfix/mysql_domains.cf
      user=mail
      password=MyMailPassword
      dbname=maildb
      table=domains
      select_field=domain
      where_field=domain
      hosts=127.0.0.1
      additional_conditions = and enabled = 1
    • Make sure all the mysl_*.cf files have their permissions set correctly
      # chmod o= /etc/postfix/mysql_*
      # chgrp postfix /etc/postfix/mysql_*
  • Now restart Postfix
    # /etc/init.d/postfix restart
    
==Database== Now we need to create the tables we just referenced in the previous section. *If you haven't done so already, give the root MySQL user a password:<pre># mysqladmin -u root password new_password
  • Now log in as root:
    # mysql -u root -p
    And enter the password for the root account when prompted.
    • Now create the mail database:
      create database maildb;
    • Next we're going to create the user that will manage and provide access to our mail database:
      GRANT SELECT,INSERT,UPDATE,DELETE ON maildb.* TO 'mail'@'localhost' IDENTIFIED by 'MyMailPassword'
      Make sure to replace MyMailPassword with your desired password.
  • Now we're going to create the tables referenced by the mysql_*.cf files:
    use maildb;
    CREATE TABLE `aliases` (
    `email` varchar(128) NOT NULL default '',
    `destination` varchar(128) NOT NULL default '',
    `enabled` tinyint(1) NOT NULL default '1',
    PRIMARY KEY (`email`)
    ) ;
    CREATE TABLE `domains` (
    `domain` varchar(120) NOT NULL default '',
    `enabled` tinyint(1) NOT NULL default '1',
    PRIMARY KEY (`domain`)
    ) ;
    CREATE TABLE `users` (
    `email` varchar(128) NOT NULL default '',
    `name` varchar(128) NOT NULL default '',
    `enabled` tinyint(1) NOT NULL default '1',
    `change_password` tinyint(1) NOT NULL default '1',
    `crypt` varchar(128) NOT NULL default 'sdtrusfX0Jj66',
    `quota` varchar(255) NOT NULL default '',
    `procmailrc` varchar(128) NOT NULL default '',
    `spamassassinrc` varchar(128) NOT NULL default '',
    PRIMARY KEY (`email`),
    ) ;
  • Exit MySQL by hitting Ctrl-d or typing in exit;.
  • Now open up MySQL's my.cnf file to check on some settings:
    # vim /etc/mysql/my.cnf
    • If it exists, make sure that the line skip-networking is commented out:
      #skip-networking
    • Make sure that the address is bound to localhost:
      bind-address = 127.0.0.1
    • If you feel like it, turn on logging for MySQL (remember to turn this off when all is well - it slows things down):
      log = /var/log/mysql/mysql.log
  • Restart MySQL to make sure any new settings are being used:
    # /etc/init.d/mysql restart

Courier

TODO: Update this with instructions for Dovecot

Courier IMAP

Now we're going to configure Courier IMAP

  • Edit /etc/courier/authdaemonrc and change the module line to the following:
    authmodulelist="authmysql"
    Also, temporarily enable a high debug level until things work.
    DEBUG_LOGIN=2
  • Edit /etc/courier/authmysqlrc and make sure the following lines are set. Make sure there is no whitespace at the end of lines.
    MYSQL_SERVER		localhost
    MYSQL_USERNAME		mail
    MYSQL_PASSWORD		MyMailPassword
    MYSQL_PORT		0
    MYSQL_DATABASE		maildb
    MYSQL_USER_TABLE	users
    MYSQL_CRYPT_PWFIELD	crypt
    # comment out this field - we don't store cleartext passwords
    #MYSQL_CLEAR_PWFIELD	clear
    #Set this only if you want people to be able to specify only their username when logging in.
    #Not a good idea if you have multiple domains you're using.
    DEFAULT_DOMAIN		pieps.org
    MYSQL_UID_FIELD	5000
    MYSQL_GID_FIELD	5000
    MYSQL_LOGIN_FIELD	email
    MYSQL_HOME_FIELD	"/var/spool/mail/virtual"
    MYSQL_NAME_FIELD	name
    MYSQL_MAILDIR_FIELD	CONCAT(SUBSTRING_INDEX(email,'@',-1),'/',SUBSTRING_INDEX(email,'@',1),'/')
    MYSQL_QUOTA_FIELD	quota
    MYSQL_WHERE_CLAUSE	enabled=1

Courier POP

Not implemented in this guide

Restart

  • Now restart courier, since we've added a bunch of new settings:
    # /etc/init.d/courier-authdaemon restart
    # /etc/init.d/courier-imap restart
    # /etc/init.d/courier-imap-ssl restart

Shorewall Part 2

Now that we have all of our services configured properly (hopefully), we can start opening up ports. Thanks to Shorewall's macro facilities, this is very simple.

  • Open up the rules file and add access for the services we've added. You may want to add more entries here later for other services (like SVN, DNS, etc.)
    # vim /etc/shorewall/rules
    Ping/ACCEPT	net		$FW
    
    # Permit all ICMP traffic FROM the firewall TO the net zone
    ACCEPT		$FW		net		icmp
    
    # mail lines
    SMTP/ACCEPT	net		$FW
    SMTPS/ACCEPT	net		$FW
    Submission/ACCEPT	net	$FW
    IMAP/ACCEPT	net		$FW
    IMAPS/ACCEPT	net		$FW
    # No POP for now, but if we decide to add it, uncomment these lines
    #POP3/ACCEPT	net		$FW
    #POP3S/ACCEPT	net		$FW
    
    #web
    Web/ACCEPT	net		$FW
  • Check the setup, and if everything is ok, test it out. Make sure not to terminate your session until you're positive you can still log in.
    # shorewall check
    # /etc/init.d/shorewall restart

Test

Now we have a basic mail server set up. This is a good point to add some data and test things out before we continue.

Data

  • Log into MySQL
    $ mysql -u mail -p maildb
  • Add data for local mail domains
    INSERT INTO domains (domain) VALUES
    ('localhost'),
    ('localhost.localdomain');
  • Add some default aliases
    INSERT INTO aliases (email,destination) VALUES
    ('postmaster@localhost','root@localhost'),
    ('sysadmin@localhost','root@localhost'),
    ('webmaster@localhost','root@localhost'),
    ('abuse@localhost','root@localhost'),
    ('mailer-daemon@localhost','postmaster@localhost'),
    ('nobody@localhost','root@localhost'),
    ('noc@localhost','root@localhost'),
    ('security@localhost','root@localhost'),
    ('@localhost','root@localhost'),
    ('@localhost.localdomain','@localhost'),
    ('root@localhost',root@pieps.org');
  • Now add some real data
    INSERT INTO domains (domain) VALUES
    ('pieps.org');
    INSERT INTO users (email, name, crypt) VALUES ('root@pieps.org', 'root',encrypt('apassword');

Incoming/Outgoing mail test

  • First we're going to turn off all services
    # /etc/init.d/courier-authdaemon stop
    # /etc/init.d/courier-imap stop
    # /etc/init.d/courier-imap-ssl stop
    # /etc/init.d/postfix stop
    # /etc/init.d/mysql stop
  • Check to make sure everything's actually stopped
    $ ps aux
    $ netstat -tnp
  • Now tail the MySQL and postfix logs on different sessons
    # tail -f /var/log/mysql/mysql.log
    # tail -f /var/log/mail.log
  • On a different session, start up your minimal server
    # /etc/init.d/mysql start
    # /etc/init.d/postfix start
  • Check to see that things are up and listening
    $ ps aux
    $ netstat -tnpl
  • Now we'll try to send a message to a local user
$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.pieps.org ESMTP Postfix
EHLO mail.pieps.org
250-mail.pieps.org
MAIL FROM: <somebody@anotherdomain.com>
250 2.1.0 Ok
RCPT TO: <root@pieps.org>
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
yams
.
250 2.0.0 Ok: queued as CACC6AC129
QUIT
221 2.0.0 Bye
  • Check your logs to make sure the mail was received and delivered properly
  • Now try to send a message from your server to a remote address
$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.pieps.org ESMTP Postfix
EHLO mail.pieps.org
250-mail.pieps.org
MAIL FROM: <root@pieps.org>
250 2.1.0 Ok
RCPT TO: <somebody@anotherdomain.com>
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
yams
.
250 2.0.0 Ok: queued as CACC6AC129
QUIT
221 2.0.0 Bye
  • Check your logs to make sure the mail was received and delivered properly
  • Turn on Courier and check that that's working
    # /etc/init.d/courier-authdaemon start
    # /etc/init.d/courier-imap start
    # /etc/init.d/courier-imap-ssl start
  • Check to see that it's working ok
$ telnet localhost 143
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
* OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION STARTTLS] Courier-IMAP ready. Copyright 1998-2008 Double Precision, Inc.  See COPYING for distribution information.
  • If you don't get this or any of this goes wrong, check over your config files again. If all else fails, Flurdy has some good suggestions.

Secondary Installation

Now that we (hopefully) have a functional mail server up and running, it's time to add some bells and whistles.

  • Install some spiffy content filtering software:
    # aptitude install amavisd-new
    # aptitude install spamassassin spamc
    # aptitude install clamav-base clamav-daemon clamav-freshclam
    # aptitude install postgrey
  • We're going to leave SquirrelMail out for now.

Amavisd-new Configuration

Amavisd is the middleman between virus/spam checkers and your MTA. We're going to first configure it to pass mail directly to Postfix, but will add in virus and spam checking in a bit.

  • All of amavis' config files are in /etc/amavis.
    cd /etc/amavis/conf.d
  • <NOTE> May need to change /etc/amavis/conf.d/05-node-id so that it points to mail.pieps.org, not www.pieps.org.
  • <NOTE> May need to set up masquerade_domains in /etc/postfix/main.cf so that it will truncate mail.pieps.org (used by /etc/amavis/conf.d/20-debian_defaults for the postmaster address), or an alias for certain addresses for mail.pieps.org. Another solution would be to edit amavis so it uses the correct addresses.
  • Edit user file
    # vim 50-user
    And insert the following in the middle
    @local_domains_acl = qw(.);
    $log_level = 2;
    $syslog_priority = 'debug';
    $sa_kill_level_deflt = 8.0; # triggers spam evasive actions
    $final_spam_destiny       = D_PASS;
    # $final_spam_destiny       = D_DISCARD;
    This sets up amavis to just pass along incoming mail.
  • Now we're going to set up postfix to play nicely with amavis
    # vim /etc/postfix/master.cf
    • Append the following lines to the end of the file (make sure they aren't present already)
      amavis      unix    -       -       -       -       2       smtp
              -o smtp_data_done_timeout=1200
              -o smtp_send_xforward_command=yes
              -o disable_dns_lookups=yes
              -o max_use=20
      127.0.0.1:10025 inet    n       -       -       -       -       smtpd
              -o content_filter=
              -o local_recipient_maps=
              -o relay_recipient_maps=
              -o smtpd_restriction_classes=
              -o smtpd_delay_reject=no
              -o smtpd_client_restrictions=permit_mynetworks,reject
              -o smtpd_helo_restrictions=
              -o smtpd_sender_restrictions=
              -o smtpd_recipient_restrictions=permit_mynetworks,reject
              -o smtpd_data_restrictions=reject_unauth_pipelining
              -o smtpd_end_of_data_restrictions=
              -o mynetworks=127.0.0.0/8
              -o smtpd_error_sleep_time=0
              -o smtpd_soft_error_limit=1001
              -o smtpd_hard_error_limit=1000
              -o smtpd_client_connection_count_limit=0
              -o smtpd_client_connection_rate_limit=0
              -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks
    • Add the following two lines immediately below the "pickup" transport service
      -o content_filter=
               -o receive_override_options=no_header_body_checks
  • Now add the following to /etc/postfix/main.cf
    content_filter = amavis:[127.0.0.1]:10024
  • Now add clamav to the amavis group, to allow it to scan amavis' temporary files
    # adduser clamav amavis
  • This should be enough to get amavis working. If emails are picked up by amavis and passed through to postfix, then all is well.

SpamAssassin Config

SpamAssassin's default settings are fine, but if spam starts getting through, it might be good to start tweaking. A good reference for that is at Flurdy.

  • The only thing you need to do is tell SpamAssassin to start
    # vim /etc/default/spamassassin
    ENABLED=1
  • One thing that could be set is the nightly update feature by setting CRON=1

ClamAV Config

Apparently, ClamAV doesn't need setting up. Config files are in /etc/clamav, but they're autogenerated so they'll get clobbered every time the defs are updated. From Flurdy:

By default freshclam, the daemon that updates the virus definition database, is run 24 times a day.
That seems a little excessive, so I tend to set that to once a day.
# dpkg-reconfigure clamav-freshclam
This will redefine the config with a lot of questions. Not needed unless you need to configure
# dpkg-reconfigure clamav-base

Postgrey Config

  • Postgrey's default config is ok, but you need to tell Postfix to use it.
    # vim /etc/postfix/main.cf
    smtpd_recipient_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination, check_policy_service inet:127.0.0.1:60000, permit
  • You can tweak whitelisting in /etc/postgrey and config in /etc/default/postgrey

Amavisd-new Config Part 2

Now that we have SpamAssassin and ClamAV set up, we can make Amavisd start filtering things

  • Open /etc/amavis/conf.d/15-content_filter_mode and uncomment the following
    @bypass_virus_checks_maps = (
       \%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re);
    @bypass_spam_checks_maps = (
       \%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re);
  • Open /etc/amavis/conf.d/50-user again and change the following
    @local_domains_acl = qw(.);
    $log_level = 1;
    $syslog_priority = 'info';
    $sa_kill_level_deflt = 8.0; # triggers spam evasive actions
    #$final_spam_destiny       = D_PASS;
    $final_spam_destiny       = D_DISCARD;

Cyrus SASL

Config

SASL allows SMTP clients to authenticate themselves, thereby keeping unauthorized clients from sending mail from your server. We're going to set it up to authenticate through plaintext (moreover, the common mechanisms that employ hashing all use insecure algorithms), so when we set up TLS (below), we're going to be tricky and force users to use it.

  • First we need to make postfix use SASL. Make sure the lines in /ect/postfix/main.cf read as follows
    smtpd_recipient_restrictions = reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_recipient, reject_unauth_destination, check_policy_service inet:127.0.0.1:60000, permit
    smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_non_fqdn_sender, reject_unknown_sender_domain, reject_unauth_pipelining, permit
    And add the following lines
    smtpd_sasl_auth_enable = yes
    broken_sasl_auth_clients = yes
  • Now we're going to configure saslauthd.
    • First, create the following directory for the SASL mux
      # mkdir -p /var/spool/postfix/var/run/saslauthd
    • Now edit /etc/default/saslauthd and turn it on
      START=yes
      Make sure it's set to use PAM
      MECHANISMS="pam"
      And set it to use the mux directory we just created
      OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd -r"
      The -r tells saslauthd to pass the entire email address instead of just the user (i.e., user@host.com instead of user)
    • <NOTE> Ubuntu Hardy seems to set the PID file location automatically based on the OPTIONS param passed to saslauthd, so in my case, I didn't have to change anything. You should check /etc/init.d/saslauthd just to make sure though, and if you're not as lucky as I was, set it to the following (or set RUN_DIR so that it's done correctly)
      PIDFILE="/var/spool/postfix/var/run/${NAME}/saslauthd.pid"
    • Next create the file /etc/pam.d/smtp and put the following into it
      auth    required   pam_mysql.so user=mail passwd=MyMailPassword host=127.0.0.1 db=maildb table=users usercolumn=email passwdcolumn=crypt crypt=1
      account sufficient pam_mysql.so user=mail passwd=MyMailPassword host=127.0.0.1 db=maildb table=users usercolumn=email passwdcolumn=crypt crypt=1
    • Now we need to create the file /etc/postfix/sasl/smtpd.conf
      pwcheck_method: saslauthd
      mech_list: plain login
      allow_plaintext: true
      auxprop_plugin: mysql
      sql_hostnames: 127.0.0.1
      sql_user: mail
      sql_passwd: MyMailPassword
      sql_database: maildb
      sql_select: select crypt from users where email = '%u'
    • We're also going to need to add postfix to sasl's group so it can access saslauthd
      # adduser postfix sasl
  • Ubuntu's default settings for SASL in Courier seem to be ok. Just make sure that IMAP_CAPABILITY_TLS="$IMAP_CAPABILITY AUTH=PLAIN" is set.
  • Restart postfix, courier, and SASL
    # /etc/init.d/postfix restart
    # /etc/init.d/courier-authdaemon restart
    # /etc/init.d/courier-imap restart
    # /etc/init.d/courier-imap-ssl restart
    # /etc/init.d/saslauthd restart

Testing

SASL is the thing that I've historically had the most trouble with. This time around I'm going with a better solution (using pam via saslauthd), but it's still good to test here before adding TLS/SSL to the mix.

  • Grab an encoded string to use to authenticate with
    $ perl -MMIME::Base64 -e 'print encode_base64("\0username\@yourdomain.com\0apassword");'
    This will give you a string that looks like AhJ3Bqfh5NplfrQa=
  • Telnet into your mail server
    $ telnet localhost 25
    Give a proper EHLO response
    EHLO mail.pieps.org
    And provide the auth string you generated above
    AUTH PLAIN AhJ3Bqfh5NplfrQa=
    The server should respond with something like
    235 2.7.0 Authentication successful
    If not, check your /var/log/mail.log and /var/log/mysql/mysql.log for details.

TLS

Encryption is a must. You don't want people eavesdropping on your login credentials. The following will detail how to set up mandatory TLS for your users to make sure that they don't expose their passwords. Note that this doesn't mean that the emails themselves will be encrypted from point to point. STARTTLS goes a short way in attempting for that to happen, but if you want real security you should use something like GnuPG.

  • First, make sure you have a valid private key and certificate. How to generate these is outside the scope of this document, but Flurdy has some good directions.
  • Self-signed keys are a start, but it's better to have a certificate signed by a CA. This is normally expensive, but there are also so-called web of trust CAs (like CAcert) that are free. These CAs still do a good job of proving that people are who they say they are, but they usually do not have their root certificates included in web browsers or mail clients; you'll have to get your users to add them manually.
  • Once you have your key and certificate ready, it's time to add a bunch of stuff to /etc/postfix/main.cf (or modify it if it's already there)
    smtp_tls_security_level = may
    smtpd_tls_security_level = may
    # Allow authentication only through TLS - this has the effect of forcing our users to use TLS
    # if they want to send mail from any host other than 127.0.0.1
    smtpd_tls_auth_only = yes
    smtp_tls_note_starttls_offer = yes
    smtpd_tls_loglevel = 1
    smtpd_tls_received_header = yes
    smtpd_tls_cert_file=/etc/postfix/postfix.cert
    smtpd_tls_key_file=/etc/postfix/postfix.key
    smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
    smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
  • Now we're going to add/change some things in /etc/postfix/master.cf. By default, only the normal smtp service is enabled, which is fine, but it's nice to enable Submission and smtps for compatibility with older clients.
    <TODO> Check over these settings a bit more
    submission inet n       -       -       -       -       smtpd
      -o smtpd_sasl_auth_enable=yes
      -o smtpd_tls_auth_only=yes
    # if you do not want to restrict it to encryption only, comment out next line
      -o smtpd_tls_security_level=encrypt
      -o smtpd_client_restrictions=permit_sasl_authenticated, reject
    # -o milter_macro_daemon_name=ORIGINATING
    smtps     inet  n       -       -       -       -       smtpd
      -o smtpd_tls_wrappermode=yes
      -o smtpd_sasl_auth_enable=yes
      -o smtpd_tls_auth_only=yes
      -o smtpd_client_restrictions=permit_sasl_authenticated,reject
    #  -o milter_macro_daemon_name=ORIGINATING
  • Finally, we're going to enable TLS in /etc/courier/imapd-ssl (this assumes that your .pem file is stored in /etc/courier/imapd.pem)
    TLS_CERTFILE=/etc/courier/imapd.pem
    If you want to restrict IMAP to SSL/TLS only, do the following
    IMAP_TLS_REQUIRED=1

Done!

You now have a secure, feature-rich mail server. Congrats!

TODO

  • Squirrelmail (or other webmail client)
    • Password change
  • GPG
  • MX backup
  • Local file backup
  • SPF
  • White/Black lists
  • Quotas
  • Auto reply
  • Mail lists
  • Output throttling

References

In no particular order. Major chunks of this guide were taken from Flurdy and Slicehost to a lesser extent.

And a bunch of forum and mailing list archives all over the internet.

Personal tools