Summary
This article describes how to set up a mail server. It does not handle spam or virus filtering, as we want to use a proxy mail server, ASSP, to remove some of the load from the mail server. Instructions on how to install the proxy server are in the article Creating a Proxy Server with ASSP
I wanted a virtual mail server that is only that; a mail server. The requirements I had were:
- easily updatable antispam and anti-virus
- A web based UI for adding/removing users and domains
- Ability to assign an "administrator" for each domain
- Ability for users to modify their own accounts
- A web based e-mail reader
- Statistics on the mail server
- imap and pop3
- runs on a Xen virtual machine
- all "important" data stored in /home for ease of backup. /home is a separate partition so it can grow
To do this, I chose postfix, postfixadmin, courier, squirrelmail, pflogsumm, spamassassin,
This "How to" is designed as a recipe howto. When I have followed howto's in the past, I have one irritant in that I feel I am constantly modifying what the user says to do. Thus, I'm trying something new.
Every place where you must insert a value, I mark the value to be replaced with a special string, and put it in italics. Thus, you can do a search/replace in many cases. Also, I have tried to set it up so that you can simply copy/paste code directly from the howto into your shell prompt and execute it.
Placeholders are defined with a leading dollar sign, followed by a string. For those of you familiar with the bash shell, you will see that this is exactly what you need to create environmental variables for your script. Thus, if your postfix install results in a uid of 207, executing the following command would place that value into the environmental variable $postfix_uid
export postfix_uid=207
Then, if you execute the command
chown -fRv $postfix_uid /home/vmail
it will substitute $postfix_uid with the correct value.
At the beginning of each section, I will give these strings for you.
Ok, lets get on with it. This document is sparse on "why" and heavy on "what". For the "why", I used the links at the end of this document. Very well written articles, in most cases, and definitely worth the effort to read.
Create the virtual
This assumes you have xen-tools installed. If you do not, install it as per this article, or simply build a server based on your standards. It also assumes Xen uses LVM to store its images and, I create a separate /home partition since that is the way
Create the virtual image
export mail_hostname=your hostname here export mail_ip=ip of new machine here export mail_home_size=initial size (in gigabytes) of home directory export mail_virtual_root=LVM Volume Group where home should be created from xen-create-image --hostname $mail_hostname --ip $mail_ip lvcreate -L $mail_home_size G -n $mail_hostname-home $mail_virtual_root # WARNING, verify the following command before executing it mke2fs -jm 0 -L $mail_hostname-home /dev/$mail_virtual_root/$mail_hostname-home # WARNING, verify the following command before executing it mount /dev/$mail_virtual_root/$mail_hostname-disk /mnt # WARNING, after this command, verify /mnt/etc/fstab has the line added, and no line breaks are necessary cat /dev/sda3 /home ext3 defaults 0 2 >> /mnt/etc/fstab umount /mnt # xen tools does not create the user directories when it creates a new virtuals, though it does copy user information # I just copy them from /home to the new volume for home mount /dev/$mail_virtual_root/$mail_hostname-home /mnt cp -av /home/* /mnt/ umount /mnt
Modify the host file to export the partion we created for /home
joe /etc/xen/$mail_hostname.cfg
Find and modify the line beginning with the word "disk = ". Just before the closing square bracket (]), insert the following. You will need to manually substitute the value for the variables. Include the leading comma.
, 'phy:$mail_virtual_root/$mail_hostname-home,sda3,w'
you might also check the values for memory, name, and IP vif to modify them as you like. Followning are some suggestions:
# SUGGESTION ONLY. This makes xm list and xm top prettier name = 'some short name' # SUGGESTION ONLY. This makes ifconfig reports easier, and gives a static mac address. On Mac, just come up with a random group of 4 hex numbers vif = ['ip=ip address of machine,vifname=short name from above0,mac=00:16:3e:df:xx:xx']
Start up new image
We are now ready to start the virtual. We will use the -c option so we can watch the console come up.
xm create /etc/xen/$mail_hostname.cfg -c
login as root, no password and immediately put a password on root:
passwd
Install basic packages
Now, lets get some very basic packages. As an old wordstar user (yes, I'm ancient), I like joe. We also need ssh, less and sudo as far as I'm concerned. I also include locales-all as something about xen-tools fails to set up the locale sometimes.
apt-get update apt-get upgrade apt-get install joe ssh less sudo locales-all lsb bzip2 zip
If you get an error about locales, use the following command to set your default locale.
dpkg-reconfigure locales
Now, secure your ssh server by editing
joe /etc/ssh/sshd_config
Edit the following lines. This assumes certificate based ssh, and that the certificate for your users were copied into their respective home directories.
Port xxxxx # use something weird. you have from 1024 to 65535. Be inventive PermitRootLogin no PasswordAuthentication no X11Forwarding no AllowUsers list of users who can log in via ssh # if you want
Restart ssh server
/etc/init.d/ssh reload
While still logged in, open another terminal and attempt a login via ssh. Fix any errors that occur before you proceed.
You can now close out of the console window from your virtual host. To do this, perform a logout, then press ctrl-] (hold the control key, then press the left square bracket). This returns you to the DOM0 screen, and you can safely exit that now. All future work assumes you are logged in via an ssh session.
Begin package installation
Now, lets install some packages. Most of these are taken from the howtoforge article, and you may not need them all. I have set them up so as many packages as possible are installed at one time.
apt-get install libhtml-parser-perl libdb-file-lock-perl libnet-dns-perl libpam-mysql metamail apt-get install binutils cpp fetchmail flex gcc libarchive-zip-perl libc6-dev libcompress-zlib-perl libdb4.3-dev libpcre3 libpopt-dev linux-kernel-headers lynx m4 make ncftp nmap openssl perl perl-modules unzip zip zlib1g-dev autoconf automake1.9 libtool bison autotools-dev g++ fam libfam0 apt-get install mysql-server mysql-client libmysqlclient15-dev postfix postfix-mysql libsasl2 sasl2-bin libsasl2-modules libdb3-util procmail courier-authdaemon courier-authlib-mysql courier-base courier-imap courier-imap-ssl courier-pop courier-pop-ssl courier-ssl libglib2.0-0
Create directories for web based administration -> no General Type of Installation -> Internet Site Mail Name -> name of this server visible to outside e-mail world
apt-get install apache2 apache2-doc apache2-mpm-prefork apache2-utils libexpat1 ssl-cert libapache2-mod-php5 php5 php5-common php5-curl php5-dev php5-gd php5-idn php-pear php5-imagick php5-imap php5-json php5-mcrypt php5-memcache php5-mhash php5-ming php5-mysql php5-ps php5-pspell php5-recode php5-sqlite php5-tidy php5-xmlrpc php5-xsl squirrelmail
Continue without maildir support -> yes
apt-get install squirrelmail-locales squirrelmail-decode libemail-valid-perl libmail-sendmail-perl libdate-calc-perl
We need the Mime::EncWords package
apt-get install libmime-encwords-perl
Configure Packages
There are some housekeeping tasks that must be done. Most of this is taken from the "Perfect Debian Setup", with some additions/modifications by me.
First, get rid of some junk:
mv /etc/cron.daily/lprng /etc/cron.daily/lprng.disabled #this is part of lsb, but it causes an error message everytime cron.daily is run
MySQL root passwords
mysqladmin -u root password yourrootsqlpassword mysqladmin -h ´hostname´ -u root password yourrootsqlpassword
Apache
joe /etc/apache2/mods-available/dir.conf # replace DirectoryIndex line with following DirectoryIndex index.html index.htm index.shtml index.cgi index.php index.php3 index.pl index.xhtml joe /etc/apache2/ports.conf # add following if it does not exist Listen 443 # Enable some apache modules a2enmod ssl a2enmod rewrite a2enmod suexec a2enmod include # restart apache /etc/init.d/apache2 force-reload
Secure PHP
joe /etc/php5/apache2/php.ini # Search for register_globals and ensure it is off
Postfix
# Create an alias for root by editing /etc/aliases, and adding the following line joe /etc/aliases # add/modify the following line. This should be an e-mail account, or name of local user root: someUserName newaliases # update alias hash # Set postfix to use maildir postconf -e 'home_mailbox = Maildir/' postconf -e 'mailbox_command =' postconf -e 'smtpd_sasl_local_domain =' postconf -e 'smtpd_sasl_auth_enable = yes' postconf -e 'smtpd_sasl_security_options = noanonymous' postconf -e 'broken_sasl_auth_clients = yes' postconf -e 'smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination' postconf -e 'inet_interfaces = all' # Create the TLS certificate mkdir /etc/postfix/ssl cd /etc/postfix/ssl/
# INTERACTIVE COMMANDS. DO NOT YOUR COPY/PASTE ONE LINE AT A TIME # the following are interactive commands. You will be asked for a password in the first command, then will need to repeat # that password for all subsequent commands. openssl genrsa -des3 -rand /etc/hosts -out smtpd.key 1024 chmod 600 smtpd.key openssl req -new -key smtpd.key -out smtpd.csr # you will need the password from above to open the file. Fill out all items, but you can leave the challenge password alone openssl x509 -req -days 3650 -in smtpd.csr -signkey smtpd.key -out smtpd.crt openssl rsa -in smtpd.key -out smtpd.key.unencrypted mv -f smtpd.key.unencrypted smtpd.key # This generates a new certificate, with a new password, so you get to fill out the certificate form again openssl req -new -x509 -extensions v3_ca -keyout cakey.pem -out cacert.pem -days 3650 # end of interactive commands # Configure Postfix for TLS postconf -e 'smtpd_tls_auth_only = no' postconf -e 'smtp_use_tls = yes' postconf -e 'smtpd_use_tls = yes' postconf -e 'smtp_tls_note_starttls_offer = yes' postconf -e 'smtpd_tls_key_file = /etc/postfix/ssl/smtpd.key' postconf -e 'smtpd_tls_cert_file = /etc/postfix/ssl/smtpd.crt' postconf -e 'smtpd_tls_CAfile = /etc/postfix/ssl/cacert.pem' postconf -e 'smtpd_tls_loglevel = 1' postconf -e 'smtpd_tls_received_header = yes' postconf -e 'smtpd_tls_session_cache_timeout = 3600s' postconf -e 'tls_random_source = dev:/dev/urandom' postconf -e 'myhostname = server1.example.com' # Make the directory for the chroot jail mkdir -p /var/spool/postfix/var/run/saslauthd
joe /etc/default/saslauthd # to activate sasl. Make to following changes START=yes OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd -r"
Create the file /etc/postfix/sasl/smtpd.comf with the following contents:
log_level: 5 pwcheck_method: saslauthd mech_list: plain login allow_plaintext: true auxprop_plugin: mysql sql_hostnames: 127.0.0.1 sql_user: postfix sql_passwd: your database password sql_database: postfix sql_select: select password from mailbox where username = '%u'
Save the file, then restart sasl and postfix:
/etc/init.d/postfix restart /etc/init.d/saslauthd start
I'm not sure this is necessary:
echo "auth required pam_mysql.so user=postfix passwd=JJ53!f1 host=127.0.0.1 db=postfix table=mailbox usercolumn=username passwdcolumn=password crypt=1" >> /etc/pam.d/smtp echo "account sufficient pam_mysql.so user=postfix passwd=JJ53!f1 host=127.0.0.1 db=postfix table=mailbox usercolumn=username passwdcolumn=password crypt=1" >> /etc/pam.d/smtp
Your postfix now has a preliminary setup. Verify by telneting to the server
telnet localhost 25 ehlo localhost
If you see the two lines:
250-STARTTLS 250-AUTH PLAIN LOGIN
Then everything is good. Type
quit
to exit.
SquirrelMail
ln -s /etc/squirrelmail/apache.conf /etc/apache2/conf.d/squirrelmail.conf /etc/init.d/apache2 reload # Create database for addresses and preferences mysql -p mysql # simply copy/paste the following SQL, but change the passwords create database squirrelmail; GRANT select,insert,update,delete ON squirrelmail.* TO squirreluser@localhost IDENTIFIED BY 'password'; connect squirrelmail; CREATE TABLE address ( owner varchar(128) DEFAULT '' NOT NULL, nickname varchar(16) DEFAULT '' NOT NULL, firstname varchar(128) DEFAULT '' NOT NULL, lastname varchar(128) DEFAULT '' NOT NULL, email varchar(128) DEFAULT '' NOT NULL, label varchar(255), PRIMARY KEY (owner,nickname), KEY firstname (firstname,lastname) ); CREATE TABLE userprefs ( user varchar(128) DEFAULT '' NOT NULL, prefkey varchar(64) DEFAULT '' NOT NULL, prefval BLOB NOT NULL, PRIMARY KEY (user,prefkey) ); quit cd /etc/squirrelmail ./conf.pl # this is an interactive command
The squirrelmail configurator is text mode menu based. You enter the letter of a menu option, and press enter. If additional data is required, a location to insert that data is opened. In the following, I list the menu options. You simply press the first letter of each option in turn.
D. Set pre-defined settings for specific IMAP servers Choose courier 1. Organization Preferences 1. Organization Name == put your company name here 5. Signout Page == I set this to /index.html R Return to Main Menu 2 Server Settings A. Update IMAP Settings 8. Server software == courier R Return to Main Menu 4. General Options 10. Allow server thread sort 11. Allow server-side sorting == true R Return to Main Menu 9. Database 1. DSN for Address Book == mysql://squirreluser:sqpassword@localhost/squirrelmail 3. DSN for Preferences == mysql://squirreluser:sqpassword@localhost/squirrelmail 8. DSN for Global Address Book == mysql://squirreluser:sqpassword@localhost/squirrelmail 9. Table for Global Address Book = address *** you can not have a user named "global" 11. Allow listing of Global Address Book R Return to Main Menu S Save data Q Quit
Edit /etc/squirrelmail/apache.conf and comment out the following lines:
joe /etc/squirrelmail/apache.conf # comment out the following block temporarily # <Files configtest.php> # order deny,allow # deny from all # allow from 127.0.0.1 # </Files>
# Restart Apache /etc/init.d/apache2 reload
Open a web browser and point it to:
http://your-mailserver/squirrelmail/src/configtest.php
And verify the configuration is correct.
joe /etc/squirrelmail/apache.conf # uncomment the block above, then restart apache <Files configtest.php> order deny,allow deny from all allow from 127.0.0.1 </Files>
# Restart Apache /etc/init.d/apache2 reload
Postfix Admin
Postifix Admin is a great program that allows you to administer your server with virtual domains. It is not a standard debian package, but it has a debian package that can be downloaded and installed
Download and install postfixadmin
cd /usr/src # Download from sourceforge. Check for a later version wget http://sourceforge.net/project/showfiles.php?group_id=191583&package_id=225300&release_id=615198 dpkg -i postfixadmin_2.2.1.1_all.deb # install
Configure postfixadmin
mysql # we need to create a database for postfixadmin create database postfix; grant all privileges on postfix.* to postfix@localhost identified by 'password'; quit joe /etc/postfixadmin/config.inc.php # make the following changes SEARCH AND REPLACE change-this-to-your.domain.tld WITH your.domain.name $CONF ['configured'] = true; $CONF['postfix_admin_url'] = 'http://mailserver host name/postfixadmin'; $CONF['database_type'] = 'mysqli'; // note the 'i' at the end $CONF['database_password'] = 'your password'; $CONF['admin_email'] = 'postmaster@domain_name'; $CONF['domain_path'] = 'YES'; $CONF['vacation'] = 'YES'; $CONF['welcome_text'] = <<<EOM Hi, ''change the text here for a new users welcome message Welcome to your new account.'' EOM; $CONF['show_undeliverable']='YES'; $CONF['create_mailbox_subdirs']=array('Spam'); $CONF['create_mailbox_subdirs']=array('NotSpam'); $CONF['create_mailbox_subdirs_host']='localhost';
$CONF['vacation'] = 'YES'; $CONF['vacation_domain'] = 'autoreply.vacation.com'; // domain does not have to be real $CONF['vacation_control'] ='YES'; $CONF['vacation_control_admin'] = 'YES';
/etc/init.d/apache2 reload # restart apache
Open a web browser and go to
http://mail server url/postfixadmin/setup.php
If you set everything up correctly, it will create your database, and request and admin e-mail account and password. Remember these! Use an e-mail account for the admin name.
rm /usr/share/postfixadmin/setup.php # this is a security risk if you leave it laying around
Open a web browser and go to
http://mail server url/postfixadmin/
And verify that it works. Do Not make any changes at this time.
Configure postfix for postfixadmin
Now, we need to figure out what the User ID (uid) and Group ID (gid) of postfix are. Issue the following command
grep postfix /etc/passwd # make note of the first (uid) and second (gid) numbers # example: postfix:x:104:105::/var/spool/postfix:/bin/false # the uid is 104, the gid is 105 (they may be the same) export postfix_uid=uid from above # no spaces except between export and postfix_uid export postfix_gid=gid from above # no spaces excetp between export and postfix_gid mkdir /home/vmail chown -fRv $postfix_uid:$postfix_gid /home/vmail postconf -e "mydomain = ´hostname -d´" postconf -e "mydestination = ´hostname -f´, localhost.´hostname -d´, localhost" postconf -e "mynetworks_style = subnet" postconf -e "smtpd_sasl_authenticated_header = yes" postconf -e "virtual_alias_maps = proxy:mysql:/etc/postfix/mysql_virtual_alias_maps.cf" postconf -e "virtual_gid_maps = static:$postfix_gid" postconf -e "virtual_mailbox_base = /home/vmail/" postconf -e "virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql_virtual_domains_maps.cf" postconf -e "virtual_mailbox_limit = 112400000" postconf -e "virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf" postconf -e "virtual_minimum_uid = $postfix_uid" postconf -e "virtual_transport = virtual" postconf -e "virtual_uid_maps = static:$postfix_uid" postconf -e "smtpd_recipient_restrictions = reject_unknown_recipient_domain,permit_mynetworks,permit_sasl_authenticated,reject_unauth_destination,permit" postconf -e "home_mailbox ="
joe /etc/postfix/mysql_virtual_alias_maps.cf # new file user = postfix password = mysql password for postfix user hosts = localhost dbname = postfix query = SELECT goto FROM alias WHERE address='%s' AND active = '1'
joe /etc/postfix/mysql_virtual_domains_maps.cf # new file user = postfix password = mysql password for postfix user hosts = localhost dbname = postfix query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'
joe /etc/postfix/mysql_virtual_mailbox_maps.cf # new file user = postfix password = mysql password for postfix user hosts = localhost dbname = postfix query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
joe /etc/postfix/mime_header_checks.regexp # (may need to adjust) (one line) /^\s*Content-(Disposition|Type).*name\s*=\s*"?(.+\.(ad[ep]|asd|ba[st]|c[ho]m|cmd|cpl|crt|dbx|dll|exe|hlp|hta|in[fs]|isp|js|jse|lnk|md[etw]|ms[cipt]|nws|ocx|ops|pcd|pi|pif|prf|reg|scf|scr|sct|sh[bms]|uue|vb|vb[esx]|vxd|wab|ws[cfh]))"?\s*$/ REJECT Files attached to emails that contain or end in "$3" are prohibited on this server as they may contain viruses. The file named "$2" was rejected.
/etc/init.d/postfix reload tail /var/log/mail.log # look for errors
Configure Vacation
# Create a user and group, with no rights, no home dir, and no login. I just manually add them echo vacation:*:65501:65501::0:0:Virtual Vacation:/nonexistent:/sbin/nologin >> /etc/passwd echo vacation:*:65501: >> /etc/group # Now, create a directory for the spool files. This will be owned only by the vacation users: mkdir /var/spool/vacation # And copy the vacation script to it cp /usr/share/doc/postfixadmin/VIRTUAL_VACATION/vacation.pl.gz /var/spool/vacation/ gunzip /var/spool/vacation/vacation.pl.gz # Change ownerships so only the vacation user can do anything with it. chown -R vacation:vacation /var/spool/vacation chmod -R 700 /var/spool/vacation joe /etc/postfix/master.cf # Add this to the bottom of the file (two lines) vacation unix - n n - - pipe flags=Rq user=vacation argv=/var/spool/vacation/vacation.pl -f ${sender} -- ${recipient} # update main.cf to use this transport postconf -e 'transport_maps = hash:/etc/postfix/transport' # Create a new file, /etc/postfix/transport with the following line echo autoreply.ddvacation.com vacation: > /etc/postfix/transport # Now, add this entry to your /etc/hosts file to ensure no dns issues echo 127.0.0.1 autoreply.ddvacation.com autoreply >> /etc/hosts # Tell Postfix to create the transports hash and to reload its configuration: postmap /etc/postfix/transport /etc/init.d/postfix reload joe /var/spool/vacation.pl Change any variables starting with $db_ or $db_type to mysql my $db_type = 'mysql'; my $db_host = 'localhost'; my $db_username = 'postfix'; my $db_password = \'database password here\'; my $db_name = 'postfix';
=== IMAP setup ===
joe /etc/courier/authmysqlrc MYSQL_USERNAME postfix MYSQL_PASSWORD database password MYSQL_DATABASE postfix MYSQL_USER_TABLE mailbox MYSQL_CRYPT_PWFIELD password MYSQL_UID_FIELD \'UID of Postfix\' MYSQL_GID_FIELD \'GID of Postfix\' MYSQL_LOGIN_FIELD username MYSQL_HOME_FIELD '/home/vmail' MYSQL_MAILDIR_FIELD maildir # uncomment MYSQL_NAME_FIELD name # You can also turn off IMAP authentication when a user is # marked as inactive. Be aware that this will cause webmail auth, # smtp relay if slaved off courier-authlib, and POP3/IMAP logins to fail. #MYSQL_WHERE_CLAUSE active='1'
joe /etc/courier/authdaemonrc # Change: authmodulelist="authmysql authpam" # be sure authmysql is first
Additional Utilities
Postfix Log Summary
I found this cute little perl script named pflogsumm that does a very nice job of giving postfix log summaries.
cd /usr/src wget http://jimsun.linxnet.com/downloads/pflogsumm-1.1.1.tar.gz tar -xzvf pflogsumm-1.1.1.tar.gz cp /usr/src/pflogsumm-1.1.1/pflogsumm.pl /usr/local/bin/pflogsumm chown bin:bin /usr/local/bin/pflogsumm chmod 755 /usr/local/bin/pflogsumm cp /usr/src/pflogsumm-1.1.1/pflogsumm.1 /usr/share/man/man1/pflogsumm.1 chown bin:bin /usr/share/man/man1/pflogsumm.1 chmod 644 /usr/share/man/man1/pflogsumm.1
Set up a schedule. Choose this, if you want to run it out of cron.d (giving you fine control on when it runs)
echo '# /etc/cron.d/pfloggsumm' > /etc/cron.d/pflogsumm echo '10 6 * * * /usr/local/bin/pflogsumm /var/log/mail.log.0 2>&1 |/usr/bin/mailx -s "´uname -n´ daily mail stats" postmaster' >> /etc/cron.d/pflogsumm chmod 644 /etc/cron.d/pflogsumm
Choose this option to have the job run with the normal daily jobs. Since syslog's job runs after this (they are done in lexical order), this will always show the previous day's work (assuming syslog rotates mail.log daily)
echo '#! /bin/bash' > /etc/cron.daily/pflogsumm echo '/usr/local/bin/pflogsumm /var/log/mail.log.0 2>&1 |/usr/bin/mailx -s "´uname -n´ daily mail stats" postmaster' >> /etc/cron.daily/pflogsumm chmod 755 /etc/cron.daily/pflogsumm
Copy e-mail from remote server using imapsync
apt-get install imapsync imapsync --dry --host1 imap.dailydata.net --user1 example.org_user --password1 'xxxxxxx' \ --host2 localhost --user2 user@example.com --password2 'xxxxxxx'
Bibliography
http://www.osreviews.net/reviews/comm/awstats
http://sourceforge.net/project/showfiles.php?group_id=191583&package_id=225300&release_id=615198
http://en.gentoo-wiki.com/wiki/Virtual_mail_server_using_Postfix,_Courier_and_PostfixAdmin
http://www.howtoforge.com/perfect_setup_debian_etch
- setting up virtual domains under postfix
http://postfix.wiki.xs4all.nl/index.php?title=Virtual_Users_and_Domains_with_Courier-IMAP_and_MySQL
- setting up maildrop for pre-delivery filtering
http://postfix.wiki.xs4all.nl/index.php?title=Combine_With_Maildrop_Howto