Building a fully fledged mail server with Exim, Cyrus-IMAP, SASL, TLS, MySQL and Debian

      Author:		   Psand Limited
      Updated By:	   $Author: nelf $
      Date:		   $Date: 2005/03/12 17:25:36 $
      Version:		   $Revision: 1.1 $
    

Introduction

We've been running email services on Unix since 1997 when we took over the maintenance of a Sun SPARC Ultra/1 box running Solaris 2.5.1. The configuration was simple and old-fashioned. It used Unix user accounts without home directories or login shells as the email accounts and Sendmail. The only service provide was POP3; not having IMAP meant no home directories for the users.

About a year after taking it over we migrated to a Intel based server running Linux and have never looked back. The configuration is essentially the same as it was back in 1997. Yes, there have been some additions. For instance, it now provides IMAP to those who want it, hence home directories; the Horde-IMP framework has been implemented to provide web mail services; Majordomo runs to manage a couple of mailing lists; we use Mhonarc to archive some mails; and a couple of the email accounts run SpamAssassin in an attempt to curtail the ever increasing Spam problem. These extras have been 'bolted-on' to the existing building block of Sendmail+Unix accounts in a haphazard manner, not incorrectly installed, but in many cases these services are still running as 'beta' and not offered to everybody.

Currently there are something in the range of 190 individual email accounts, for 130 domains with 250 virtual user aliases. It's not a big system, but it's out of date and given that managing the mail server is not something we have a lot of time to do (spare or work), it's got out of hand, not in the least because of the large amount of Spam that seems to circulate around the network. The mail traffic report generated by AWStats for April 2004 shows a total of 79,581 emails sent and 19,141 rejected, totally approximately 5GB of traffic, which seems to be quite a lot to me.

With all this in mind, we felt that it was about time that we had a shiny new mail server with all the latest in services and Spam protection. In this document we hope to detail what we chose to use and why and what my experiences were with getting it working.

For reference, the server in question is running Debian GNU/Linux 3.0 (Woody) with kernel version 2.4.25, so these instructions are tested on Debian; however, we believe they should work on all Linux distributions (at least those with a 2.4.x kernel) as well as *BSD, Mac OS X and the hoard of other Unicen out there.

Aims for new server

  1. To do away with using Unix user accounts. It would be much better if we could store the accounts in a database or similar (we use MySQL) rather than use Unix user accounts. This would mean that we can do away with any user logins (apart from Unix admin accounts) on the server entirely, tying down the security a little.
  2. Be able to offer encrypted email. It would be great if all email could be automatically encrypted between the mail client and server when sending and receiving, as well as between mail servers. (TLS/SSL)
  3. Provide both POP3 and IMAP services to all users.
  4. Provide web mail service to all users.
  5. Provide a system-wide spam filter for all users.
  6. Provide a web page where users can easily retreive their password if forgotten.
  7. Allow authenticated users to send email through the mail server (SASL).
  8. Produce decent mail statistics to monitor account usage.
  9. Implement SPF records in DNS and MTA and use domain and right-hand black-lists.
  10. Allow users the ability to have away messages set on their accounts for when they go on holiday or get fired.
  11. Be able to administrate the whole thing, quickly, easily and remotely, preferably from any modern web browser.

Software choices

Bit duff this section, perhaps remove?

Mail Transport Agent (MTA)

We've had the Exim MTA running for quite a while on one of our test servers. It seems to be good, light-weight and much easier to manage than Sendmail. So we chose it. There are others we know, Postfix, Qmail etc, but we don't have time to test them all and in short, Exim is good, it works and it's my choice here. Sendmail's been a faithful servant over the years, but the arthritis is setting in and we think that it's been high-time to move onto pastures new for quite a while now.

TLS

Testing installation:

apt-get install exim-tls mailx

Send an email to the user (adelayde@mango.psand.net). Then check this box on the server (/var/spool/mail/adelayde) does the message arrive okay? Cool Exim is working.

SASL: how to authenticate against a MySQL database?

How to recognise recipient names?

Cyrus IMAP Server

Seems like a good package, Cyrus uses it's own directory structure, so bang go the Unix home directories. By default it handles authentication through either Unix shadow passwords or Kerberos. The latter seems a little bit too complicated for this set-up and yet something else to learn and the former is what we're trying to get away from. So we need it to authenticate against our MySQL database.

MySQL

Installing Cyrus-IMAP

It all comes in deb packages, so get apt-getting:

apt-get install cyrus-imapd cyrus-common cyrus-pop3d cyrus-admin

Cyrus authenticating against SASLdb

By default, Cyrus authenticates against SASL using its own internal DB file, read the file /usr/share/doc/cyrus21-doc/README.Debian.simpleinstall.gz for handy tips. Key point is to set-up the cyrus user:

Cyrus Authentication using SASL and MySQL

First: Read the file /usr/share/doc/libsasl2/index.html

Second, note that we're not using PAM authentication here as we previously thought, mainly because we can't get this to work. It's enough (and perhaps simpler) to do just SASL+MySQL. Nonetheless in the next bit, I've detailed the Cyrus+PAM+MySQL notes.

Go back and edit the imapd.conf file and change the following:

    sasl_mech_list: PLAIN DIGEST-MD5 CRAM-MD5
    sasl_minimum_layer: 0
    sasl_pwcheck_method: auxprop
    sasl_auxprop_plugin: sql
    sasl_sql_engine: mysql
    sasl_sql_hostnames: localhost
    sasl_sql_user: cyrus
    sasl_sql_passwd: *********
    sasl_sql_database: exim
    sasl_sql_select: SELECT password AS userPassword FROM users WHERE username = '%u' AND enabled = '1'    
    

And restart the IMAP daemon to read in the new settings:

/etc/init.d/imap restart

Now create a database and table for authenticating your IMAP/POP3 mailbox users, for example:

      USE exim;
      CREATE TABLE users (
      id INT(11) NOT NULL AUTO_INCREMENT,
      username VARCHAR(15) DEFAULT '' NOT NULL,
      password VARCHAR(15) DEFAULT '' NOT NULL,
      enabled TINYINT DEFAULT 1 NOT NULL,
      
      email VARCHAR(40) DEFAULT '' NOT NULL,
      name VARCHAR(50) DEFAULT '' NOT NULL,
      company VARCHAR(30) DEFAULT '' NOT NULL,
      tel1 VARCHAR(15) DEFAULT '' NOT NULL,
      tel2 VARCHAR(15) DEFAULT '' NOT NULL,
      domain VARCHAR(40) DEFAULT '' NOT NULL,
      
      PRIMARY KEY (id)
      ) TYPE=MyISAM;
    

Forth thing, grant permission to a user in MySQL:

      GRANT SELECT, INDEX, INSERT, UPDATE, DELETE ON exim.users TO cyrus@localhost IDENTIFIED BY 'cyrusIMAP';
      FLUSH PRIVILEGES
    

Now you add users to the exim.users table:

    INSERT INTO users (username, password) VALUES ('username1','password1');
    

Flakey.info warning

You can encrypt the password by putting the result of the following in instead of the plain text password:

      SELECT PASSWORD('password1');
    

Will return an encyrpted string (i.e. 6C5acede3d3de7e4), use this in the INSERT statement above.

NOTE: ** we found it not to work with MySQL PASSWORD authentication, not sure why, so we used the built-in MD5 encryption instead, i.e.:

      a. mkpasswd password1
      b. udpate mysql table.
      c. change crypt=1 in cyrus config.
    

End of Flakey.info warning


Having done that it then should really just work. You can try using the cyradm command for your user:

      cyradm -u cyrus mango.psand.net	
    

Note in order that a user can login via cyradm, you must list the username in the file /etc/imap.conf, under the entry 'admins:', for example:

      admins: user1
    

Cyrus + PAM + MySQL Authentication

This can be done by using PAM. Cyrus can authenticate against PAM and PAM has a MySQL plug-in for authenticating against a database. Check out http://www.delouw.ch/linux/Postfix-Cyrus-Web-cyradm-HOWTO/html/pam-config.html for some information on this. We went for SASL, but here are the details for the PAM way, which in the end didn't see to work very well.

Edit /etc/pam.d/cyrus and replace the lines:

      auth           	required                pam_unix.so nullok
      account           required                pam_unix.so
    

with:

      auth       sufficient   pam_mysql.so user=cyrus passwd=******* \
       host=localhost db=mailusers table=mailusers usercolumn=username \
       passwdcolumn=password crypt=0 where=enabled=1
      account    required     pam_mysql.so user=cyrus passwd=******* \
       host=localhost db=mailusers table=mailusers usercolumn=username \
       passwdcolumn=password crypt=0 where=enabled=1
    

Then do update-alternatives --display pwcheck

and if it returns 'link currently points to /usr/sbin/pwcheck_standard'

do,

      update-alternatives --config pwcheck
    

and you'll see:

There are 2 programs which provide `pwcheck'.

      Selection    Command
      -----------------------------------------------
      *+    1        /usr/sbin/pwcheck_standard
            2        /usr/sbin/pwcheck_pam
    

Enter to keep the default[*], or type selection number:
and type in '2':
you'll get:
Using `/usr/sbin/pwcheck_pam' to provide `pwcheck'.

So do then,

      /etc/init.d/pwcheck restart
    

Test the connexion locally:

      cyradm -user cyrus localhost
    

Adding TLS Support to Cyrus IMAP

To add TLS/SSL support to your server, you need to again edit imapd.conf and set the following options:

      tls_cert_file: /etc/ssl/certs/cyrus-global.pem
      tls_key_file: /etc/ssl/private/cyrus-global.key
      tls_ca_path: /etc/ssl/certs
      tls_session_timeout: 1440
      tls_cipher_list: TLSv1:SSLv3:SSLv2:!NULL:!EXPORT:!DES:!LOW:@STRENGTH
    

Now we need to create the key file and certificate. To do this we need to use Open SSL:

      cd /etc/ssl/
      openssl req -new -nodes -out certs/cyrus-global.csr \
        -keyout private/cyrus-global.key
      openssl rsa -in private/cyrus-global.key \
        -out private/cyrus-global.key 
      openssl x509 -in certs/cyrus-global.csr \
        -out certs/cyrus-global.pem -req \
        -signkey private/cyrus-global.key -days 3650
      chgrp mail private
      chmod g+rx private
      chmod o-rwx private/cyrus-global.key
    

Note, in the above, need to check the security aspects of allowing access to mail user to that directory. Unfortunately the documentation suggests the above, but also under Debian, cyrus process runs as group 'mail'. There's probably some handy deb script or package for the above, but we couldn't find it.

To make Exim use Cyrus for the delivery of email messages add the following to your /etc/exim/exim.conf file (and comment out the default entry for 'local_delivery') Really this is now out of the correct order, as this uses MYSQL to deal with the local users see towards the end of the document for information about this:

      virtual_local_md_delivery:
        driver = pipe
        command = /usr/sbin/cyrdeliver \
          "${lookup mysql {MYSQL_Q_BOXNAME}{$value}}"
	user= cyrus
	group = mail
	return_fail_output
	log_output
	message_prefix =
	message_suffix =
    

10.How to make Exim authenticate the incoming user against mysql?

--- Dealing with Virtual Users.

1.Create the following MySQL table in the mailusers database:

      CREATE TABLE virtusers (
        id int(11) NOT NULL auto_increment,
	localpart varchar(20) DEFAULT '' NOT NULL,
	domain    VARCHAR(50) DEFAULT '' NOT NULL,
	enabled TINYINT DEFAULT 1 NOT NULL,
	target VARCHAR(100) DEFAULT '' NOT NULL,
	
	name varchar(50) DEFAULT '' NOT NULL,
	company varchar(30) DEFAULT '' NOT NULL,
	tel1 varchar(15) DEFAULT '' NOT NULL,
	tel2 varchar(15) DEFAULT '' NOT NULL,
	
	PRIMARY KEY (id)
      ) TYPE=MyISAM;
    

2.Here's some test users:

See below in the exim section.

3.Exim.conf configuration for this:

See below

Exim Configuration Notes

Exim is great, but it's not so great that you don't need to submerge your brain in a bucket of LSD to get your head round how it all works... mail is not simple, but exim tackles the whole thing in a reasonably sensible, flexible and well documented way.

Here follows a list of desirable features from Exim:

Feature Status Description
Virtual Users Fully Implemented any_user@any_domain domain can have mail delivered to any local cyrus mailbox or redirected elsewhere.
Spam filtering Fully implemented (spamassassin) Any mail considered to be spam is marked as such and can thus be filtered into a spam mailbox by the user's client machine
Spam dumping (spammassissin) Implemented, but not tested Any mail considered to be spam will be dumped automatically into /dev/null
Away messages Implemented but not tested A virtual user can be marked as away and a customisable message is sent back to the sender
Virus Scanning Transport implemented, virus scanner not implemented Mails containing viruses will be blocked
Disabling of accounts Implemented but not tested Any particular virtual user can be disabled temporarily without removing the virtuser entry.
SMTP service for clients Fully implemented A user anywhere can use the server as a relay if they have the right secret knowledge
TLS Fully implemented All services should be available over an encrypted SSL/TLS connection

Starting And Stopping the Exim Daemon

As you might expect, exim is started using the following command:

      # invoke-rc.d exim4 start
    

If you want to send any extra command line parameters to the daemon, then you should use the /etc/defaults/exim4 file to do so. A particularly useful one is the '-v' option that causes the exim daemon to keep STDOUT open when you run invoke-rc.d exim4 start and you can see lots of information about what is happening... you put this in the 'SMTPLISTENEROPTIONS section of the /etc/default/exim4 file.

Configuration Files

/etc/exim4/update-exim4.conf.conf

This is the 'meta configuration' file... not a lot gets set here, but what does get set is very important. It's a small file and I'll include it here for completeness - I've added comments at the end of each line to help understand what goes on:


      # /etc/exim4/update-exim4.conf.conf
      #
      # Edit this file and /etc/mailname by hand and execute update-exim4.conf
      # yourself or use 'dpkg-reconfigure exim4-config'

      dc_eximconfig_configtype='internet'
      dc_other_hostnames='psand.net,iain.biz'
      dc_local_interfaces=''
      dc_readhost=''
      dc_relay_domains='nelf.net'
      dc_minimaldns='false'
      dc_relay_nets='194.164.97.0/24' # Who is allowed to use the server without authenticating
      dc_smarthost=''
      CFILEMODE='644'
      dc_use_split_config='false' # For different approaches to configuration file layout
    

/etc/exim4/exim4.conf.template

Basic configuration is defined in this file. This is where the exim binaries are defined, log and spool files are defined, the users that are allowed to fiddle with mail, MYSQL connections and statements are defined here too. See below for a description of how the MYSQL stuff works.

MySQL tables

The following is the set of mysql tables used by exim to handle virtual users, etc. These tables are all in a database called exim so as not to interfere with any of Mikes setup:

virtualusers Each row defines a single 'virtual user', and what is going on with them (ie the local part and domain part, which mailbox the mail is sent to, whether it is virus scanned and spam scanned, whether that virtual user is away or not and the message that is displayed etc etc.
domaintable Very simple table that says which domains exim will deal with - equivalent of local domains in sendmail
users Very simple table that defines user names and passwords for the actual mailboxes, used by Cyrus to authenticate users via IMAP or POP3. Table also contains some extra fields for the reference of administrators. This is equivalent of UNIX user accounts and the /etc/passwd file.
relaytableSimple table which defines which domains this server will relay for (rarely used)
whitelist This contains addresses that should never be filtered out as spam.
blacklist This blacklists certain sender addresses and prevents mail from being processed that has this sender in it.
      create database exim;

      CREATE TABLE `virtualuserss` (
        `local_part` varchar(20) NOT NULL default '',
        `domain` varchar(30) NOT NULL default '',
	`forward` varchar(50) NOT NULL default '',
        `box` varchar(30) default NULL,
	`is_away` enum('yes','no') NOT NULL default 'no',
	`away_text` tinytext NOT NULL,
	`is_enabled` enum('yes','no') NOT NULL default 'yes',
	`opt_virscan` enum('yes','no') NOT NULL default 'no',
	`opt_spamscan` enum('yes','no') NOT NULL default 'yes',
	`opt_spampurge` enum('yes','no') NOT NULL default 'no',
	`lc_ts` int(11) NOT NULL default '0',
	`lc_ip` varchar(15) NOT NULL default '',
	`create_ts` int(11) NOT NULL default '0'
      ) TYPE=MyISAM;

      CREATE TABLE `domaintable` (
        `domain` varchar(30) NOT NULL default ''
      ) TYPE=MyISAM;

      CREATE TABLE `relaytable` (
        `domain` varchar(50) NOT NULL default ''
      ) TYPE=MyISAM;

       CREATE TABLE `whitelist` (
         `address` varchar(50) NOT NULL default ''
       ) TYPE=MyISAM;

       CREATE TABLE `blacklist` (
         `address` varchar(50) NOT NULL default ''
       ) TYPE=MyISAM;
    

The MYSQL connection and statements are at the top of the /etc/exim4/exim4.conf.template:

      # MySQL defines
      MYSQL_SERVER=localhost
      MYSQL_USER=exim
      MYSQL_PASSWORD=*******
      MYSQL_DB=exim
      MYSQL_EMAILTABLE=virtualusers
      MYSQL_DOMAINTABLE=domaintable
      MYSQL_DOMAINRTABLE=relaytable
      MYSQL_WHITETABLE=whitelist
      MYSQL_BLACKTABLE=blacklist
      MYSQL_AUTHTABLE=boxauth

      # MySQL queries

      MYSQL_Q_ISAWAY=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' \
       AND local_part='${quote_mysql:$local_part}' AND is_away='yes'

      MYSQL_Q_AWAYTEXT=SELECT away_text FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' \
       AND local_part='${quote_mysql:$local_part}'

      MYSQL_Q_FORWARD=SELECT forward FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' \
º      AND local_part='${quote_mysql:$local_part}' AND forward != ''

      MYSQL_Q_LOCAL=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' \
      AND local_part='${quote_mysql:$local_part}' AND box != ''

      MYSQL_Q_WCLOCAL=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' \
      AND local_part='*' AND box != ''
      
      MYSQL_Q_WCLOCFW=SELECT forward FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' \
      AND local_part='*' AND forward != ''

      MYSQL_Q_LDOMAIN=SELECT DISTINCT domain FROM MYSQL_DOMAINTABLE WHERE domain='$domain'

      MYSQL_Q_RDOMAIN=SELECT DISTINCT domain FROM MYSQL_DOMAINRTABLE WHERE domain='$domain'
      
      MYSQL_Q_BOXNAME=SELECT box FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' \
      AND local_part='${quote_mysql:$local_part}'

      MYSQL_Q_SPAMC=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' \
      AND local_part='${quote_mysql:$local_part}' AND opt_spamscan='yes'

      MYSQL_Q_VSCAN=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' \
      AND local_part='${quote_mysql:$local_part}' AND opt_virscan='yes'

      MYSQL_Q_SPAMPURGE=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' \
      AND local_part='${quote_mysql:$local_part}' AND opt_spampurge='yes'

      MYSQL_Q_DISABLED=SELECT domain FROM MYSQL_EMAILTABLE WHERE domain='${quote_mysql:$domain}' \
      AND local_part='${quote_mysql:$local_part}' AND is_enabled='no'

      MYSQL_Q_WHITELIST=SELECT DISTINCT MYSQL_WHITETABLE.address FROM MYSQL_WHITETABLE \
      WHERE whitelist.address='${quote_mysql:$sender_address}' \
      OR whitelist.address='*@${quote_mysql:$sender_address_domain}'

      MYSQL_Q_BLACKLIST=SELECT MYSQL_BLACKTABLE.address FROM MYSQL_BLACKTABLE \
      WHERE blacklist.address='${quote_mysql:$sender_address}' \
      OR blacklist.address='*@${domain:${quote_mysql:$sender_address}}' LIMIT 1

      MYSQL_Q_AUTHPWD1=SELECT boxname FROM MYSQL_AUTHTABLE WHERE boxname='$2' AND boxpwd=encrypt('$3',boxpwd)

      MYSQL_Q_AUTHPWD2=SELECT boxname FROM MYSQL_AUTHTABLE WHERE boxname='$1' AND boxpwd=encrypt('$2',boxpwd)

# MySQL connection
hide mysql_servers = "MYSQL_SERVER/MYSQL_DB/MYSQL_USER/MYSQL_PASSWORD"

    

Local Domains

Here is how the domaintable is used to determine whether an incoming domain is local or not:

      domainlist local_domains = mysql;MYSQL_Q_LDOMAIN
    

Routers

      ######################################################################
      #                      ROUTERS CONFIGURATION                         #
      #               Specifies how addresses are handled                  #
      ######################################################################
      #     THE ORDER IN WHICH THE ROUTERS ARE DEFINED IS IMPORTANT!       #
      # An address is passed to each router in turn until it is accepted.  #
      ######################################################################

      begin routers
      
      fail_router:
        debug_print = "R: fail_router for $local_part@$domain"
	driver = redirect
	domains = ${lookup mysql {MYSQL_Q_DISABLED}{$value}}
	data = ":fail:"
	allow_fail


	...
	...

	blacklist_router:
	  debug_print = "R: blacklist_router for $local_part@$domain"
	  driver = manualroute
	  senders = ${lookup mysql {MYSQL_Q_BLACKLIST}{$value}}
	  condition = "${if !def:h_X-Spam-Flag: {1}{0}}"
	  headers_add = X-Spam-Flag: YES
	  route_list = * localhost
	  self = pass

	...
	...


	spamcheck_director:
	  debug_print = "R: spamcheck_director for $local_part@$domain"
	  driver = manualroute
	  domains = ${lookup mysql {MYSQL_Q_SPAMC}{$value}}
	  senders = ! ${lookup mysql {MYSQL_Q_WHITELIST}{$value}}
	  condition = ${if and { {!def:h_X-Spam-Flag:} {!eq {$received_protocol}{spam-s\
	  canned}} {!eq {$received_protocol}{local}} } {1}{0}}
	  route_list = "* localhost byname"
	  transport = spamcheck
	  verify = false

	spampurge_director:
	  driver = manualroute
	  domains = ${lookup mysql {MYSQL_Q_SPAMPURGE}{$value}}
	  condition = "${if eq{$h_X-Spam-Flag:}{YES} {1}{0}}"
	  route_list = "* localhost byname"
	  transport = devnull_transport
	  verify = false
    

Vacation

	vacation_director:
	  driver = accept
	  domains = ${lookup mysql {MYSQL_Q_ISAWAY}{$value}}
	  transport = vacation_autoreply
	  unseen

	virtual_forward_director:
	  driver = redirect
	  data = ${lookup mysql {MYSQL_Q_FORWARD}{$value}}
	  
	virtual_local_mailbox:
	  debug_print = "R: virtual_local_mailbox for $local_part@$domain"
	  driver = accept
	  domains = ${lookup mysql {MYSQL_Q_LOCAL}{$value}}
	  transport = virtual_local_md_delivery


	virtual_wclocal_redirect:
	  driver = redirect
	  domains = ${lookup mysql {MYSQL_Q_WCLOCAL}{$value}}
	  data = ${lookup mysql {MYSQL_Q_WCLOCFW}{$value}}

    

Adding Mailboxes for Exim

Firstly, all actual mail is delivered by exim to CYRUS, so if no CYRUS mailbox exists, then things are going to go very badly indeed!

Assuming a cyrus mailbox called 'brian' exists, in order to make exim accept mail for brianjones@whoknows.com the following things need to be done:

Add the domain part to the exim.domaintable table:

      INSERT INTO domaintable set domain='whoknows.com';
    

Now set up the virtual user part:

      INSERT INTO virtualusers SET local_part='brianjones', \
       domain='slackmail.co.uk', box='brian', opt_spamscan='yes';
    

Setting up Horde, IMP and other apps for web mail

Before we start, you really ought to have some web servers running:

      apt-get install apache-ssl apache logrotate
    

The Debian packages work fine, but can confuse you if you're already familiar with Horde as it moves things around. Firstly let's install it:

Horde package initial installation

      apt-get install horde2 imp3 turba kronolith mnemo nag
    

Now you'll find that you have the following:

Which is pretty sensible when you think that the config files are put in a much more secure place by default on Debian. Anyway on with the show:

Apache server set-up

Here we're going to use the SSL enabled version of Apache, you really wouldn't want to use the non-SSL version. Firstly need to edit our Apache-SSL configuration to enable PHP support and use Horde, so bring up /etc/httpds.conf in your favourite editor.

Look for the following line and make sure it's uncommented:

      AddType application/x-httpd-php .php
    

Look for the DirectoryIndex directive and make index.php the home page:

      <IfModule mod_dir.c>
        DirectoryIndex index.php index.html
      </IfModule>
    

Change DocumentRoot:

      DocumentRoot /usr/share/horde2/imp/
    

Change the Directory entry for the above:

      <Directory /var/www/horde2/imp/>
      ....
      ....
      </Directory>
    

Check right at the bottom of the file that the Horde config is included:

      Include /etc/horde2/apache.conf
    

Now we need to make sure that the PHP4 module is loaded, by default under Debian it doesn't see to get added to the list of modules, so do this manually. Edit /etc/apache-ssl/modules.conf and insert the following line at the bottom of it:

      LoadModule php4_module /usr/lib/apache/1.3/libphp4.so
    

While you're there, I'd also recommend commenting out a few things we don't need and could be considered insecure:

      # LoadModule status_module /usr/lib/apache/1.3/mod_status.so
      # LoadModule autoindex_module /usr/lib/apache/1.3/mod_autoindex.so
      # LoadModule userdir_module /usr/lib/apache/1.3/mod_userdir.so
    

Last but not least, and returning to httpd.conf, there seems to be a bug in the default Debian install of Apache-SSL (at the time of writing), it seems that all modules.conf loads the SSL version of mod_mime, httpd.conf still looks for the non-SSL version. The net result is that the PHP file type isn't recognised as is therefore sent to you to be downloaded, rather than being run. To fix this, change the following line:

      <IfModule mod_mime.c>
    

To read ...

      <IfModule mod_mime_ssl.c>
    

Next we need to produce a signed SSL certificate for Apache and set-up our httpd.conf file to use it. You may wish to spunk your money on paying one of the many organisations that sign certificates. However here we self-sign them and save ourselves some wonga:

      # cd /etc/apache-ssl/
      # openssl genrsa -out /etc/ssl/private/www.yourwebmaildomain.com.key 1024
      # openssl req -new -key www.yourwebmaildomain.com.key -out www.yourwebmaildomain.com.csr
    

You'll be asked to provide information for the certificate, I for example used the following for our web mail set-up. Obviously customise this to suit your specific site:

      Country Name (2 letter code) [AU]:UK
      State or Province Name (full name) [Some-State]:Kent
      Locality Name (eg, city) []:Ramsgate
      Organization Name (eg, company) [Internet Widgits Pty Ltd]:Psand Ltd
      Organizational Unit Name (eg, section) []:Slackmail
      Common Name (eg, YOUR name) []:www.slackmail.co.uk
      Email Address []:support@psand.net

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

At this stage, if you were to buy a third-party-signed certificate, you would send the .key file to the signer and wait for a .csr file to come back. In this case we sign it ourselves.

      # openssl X509 -req -days 1825 -in www.yourwebmaildomain.com.csr -signkey www.yourwebmaildomain.com.key -out www.yourwebmaildomain.com.crt 
    

Note in the above command, I specified 1,825 days or five years. You can choose yourself how long you'd like the certificate to last. If it all worked okay you should get something like the following as the result:

      Signature ok
      subject=/C=UK/ST=Kent/L=Ramsgate/O=Psand Ltd/OU=Slackmail/CN=www.slackmail.co.uk/emailAddress=support@psand.net
      Getting Private key
    

Now edit the file /etc/apache-ssl/httpd.conf and change/create the following lines:

      SSLCertificateFile /etc/apache-ssl/www.yourwebmaildomain.com.crt
      SSLCertificateKeyFile /etc/ssl/private/www.yourwebmaildomain.com.key

Finishing the Horde set-up

Restarting Apache-SSL should mean that it will work to some extent, you can test horde in your browser by visiting 'http://hostname/horde/test.php

We did this and got some warning about missing packages. We then went about installing some more debian modules:

      apt-get install php-file php-date php-mcyrpt php-domxml php-mcal
    

And some PEAR modules:

      pear install Mail_Mime 
      pear install Net_Socket
      pear install Log
    

I needed to do 'pear config-set preferred_state beta' in order to be able to then install and just in case, I set the package status back again afterwards:

      pear config-set preferred_state beta
      pear install HTML_Common
      pear install HTML_Select
      pear config-set preferred_state stable
    

And restarted the web server again.

Note on database authentication with Horde

Horde needs access to the 'horde2' database in MySQL to save various session things, including user preferences (keyed by email address). The file which controls this (if you ever wish to change the password or user) is /var/lib/horde2/horde-debian.php and our current settings are:

      $conf['prefs']['driver'] = 'sql';
      $conf['prefs']['params']['phptype'] = 'mysql';
      $conf['prefs']['params']['hostspec'] = 'localhost';
      $conf['prefs']['params']['username'] = 'horde';
      $conf['prefs']['params']['password'] = '*******';
      $conf['prefs']['params']['database'] = 'horde2';
      $conf['prefs']['params']['port'] = '3306';
      $conf['prefs']['params']['table'] = 'horde_prefs';
    

Now for additional IMP set-up

Now on with IMP, first edit /etc/horde/registry.php to allow IMP to handle Horde logging in, done by looking for and enabling the following lines:

      $this->registry['auth']['login'] = 'imp';
      $this->registry['auth']['logout'] = 'imp';
    

Decided to add a few extra email headers to show more about where the message came from and to send extra information, edit file /usr/share/horde2/imp/config/header.txt and add:

      X-WebMail-Company: Psand Ltd. (www.psand.net)
      X-Originating-IP: %REMOTE_ADDR%
      X-Originating-UA: %HTTP_USER_AGENT%
    

At this point you can also edit the text that is always postpended to each mail sent from IMP. The contents are in the file /usr/share/horde2/imp/config/trailer.txt.

Lastly to enable and disable these headers and trailers, you edit the file /etc/imp3/conf.php. In this instance, I enabled (or kept) the header, but disabled the trailer:

      $conf['msg']['prepend_header'] = true;
      $conf['msg']['append_trailer'] = false;
    

Test your IMP set-up with by visiting https://slackmail.psand.net/horde/imp/test.php

From this page, you could try entering the following values for testing against a Cyrus IMAP server with TLS support:

      Server: mango.psand.net	
      Protocol: imap/ssl/novalidate-cert
      Port: 993 
      User: user1
      Password: password1
    

If that worked, you can connect to Cyrus IMAP via IMP fine, so now we can edit the default server that it authenticates against for login. Edit the file /var/lib/imp3/servers-debian.conf and change the entry that's in there to read like below:

      $servers['imap'] = array(
        'name' => 'IMAP Server',
        'server' => 'localhost',
        'protocol' => 'imap/ssl/novalidate-cert',
        'port' => 993,
        'folders' => 'INBOX.',
        'namespace' => '',
        'smtphost' => 'localhost',
        'maildomain' => 'psand.net',
        'realm' => 'psand.net',
        'preferred' => ''
      );
     

Other settings of interest for Horde and IMP

In /etc/horde2/horde.php set the email address for reporting problems:

      $conf['problems']['enabled'] = true;
      $conf['problems']['email'] = 'support@slackmail.co.uk';
    

In /etc/imp3/conf.php, there's a few things one can change:

      $conf['user']['select_sentmail_folder'] = true;
      $conf['user']['allow_resume_all_in_drafts'] = true;
      $conf['spam']['reporting'] = true;
      // $conf['spam']['email'] ....
      $conf['spam']['program'] = '/usr/bin/spamassassin -r';
    

Multi-lingual support in Horde and IMP

We support people who speak different languages than English. Now although many people can understand English to a greater or lesser degree, it is still important to provide them where possible with tools in their language; very often people have a grasp of English and not full comprehension of it, therefore they might miss an important piece of information, also in my experience people tend to prefer things in their own language as they feel less colonialised. Anyway there's two parts to this, getting Horde and IMP's interfaces to appear in the language of the user's choice and getting in to support spell checking in languages other than American.

Firstly, you're going to need to make sure that the server has the relevant packages, for this we do some apt-getting, starting with some spelling dictionaries (most supported languages I'll install, that gives us the best support for all our users):

      apt-get install ispell iamerican ibrazilian ibritish ibulgarian \
       iczech icatalan idutch iesperanto ifaroese ifinnish ifrench igaelic \
       ihungarian iirish iitalian ilithuanian imanx ingerman inorwegian \
       ipolish iportuguese irussian ispanish iswedish iukrainian \
       brazilian-conjugate wbulgarian wcatalan wdutch wfaroese wfinnish \
       wfrench witalian wngerman wpolish wspanish wswedish wukrainian
    

Now we enable IMP to use ispell by editing the file /etc/imp3/conf/ and setting the following:

      $conf['utils']['spellchecker'] = 'ispell';
    

To take advantage of Horde's multi-lingual support, we need gettext:

      apt-get install gettext gettext-doc gettext-base gettext-el
    

Next up is installing the locales on the system so that Horde can use them to display it's interface in different languages, let's get apt-getting again:

      apt-get install locales
    

Skip the configuration for this (cancel it), you can always run it later using the command 'dpkg-reconfigure locales'. However there's no real reason to do this, selecting all the locales from the list is a bind, there's a short-cut you can use to generate all the supported locales:

      cat /usr/share/i18n/SUPPORTED >> /etc/locale.gen
    

Open the file /etc/locale.gen in your favourite text editor, search for the locale byn_ER UTF-8 and comment it out or remove it. Now do:

      locale-gen
    

That may take a few minutes. Once done, you then need to make sure PHP supports gettext. This took me most of a day to figure out as I couldn't find explicit 'dependencies' in the instructions. I eventually found it in the Horde FAQ under SUSE. Anyway, what you need to do is to edit /etc/php/apache/php.ini and under the extensions setion add:

      extension=gettext.so
    

and restart your web server.

Now we set our preferred language to use, in our case British English, do this by editing /etc/horde2/lang.php:

      $nls['defaults']['language'] = 'en_GB';
    

Also in this file there's a setting for the dictionary to use, ispell has a Catalan dictionary, but we need to add it for IMP:

      $nls['spelling']['ca_ES'] = '-d catalan';
    

Viewing attachments in IMP

Need to install some file viewers to view PDF, Word etc on the fly in one's browser window. This is done by doing an 'apt-get install wv ppthtml enscript zip unzip rar pdftohtml' and then by editing /etc/horde2/mime_drivers.php and the changing the following:

	  $mime_drivers_map['horde']['registered'] = array(
	  'php', 'tgz', 'vcard', 'enriched', 'images'
	  ,'msword', 'msexcel', 'mspowerpoint'
	  ,'enscript', 'tar', 'zip', 'rar'
	  , 'rpm', 'deb'
	  );
	

Then find the section beginning $mime_drivers['horde']['msword']['location'] ... and uncomment that. Then do the same for the xlhtml (excel) section and change the path to the exe, removing the local part:

	  $mime_drivers['horde']['msexcel']['location'] = '/usr/bin/xlhtml';
	
and for powerpoint
	  $mime_drivers['horde']['mspowerpoint']['location'] = '/usr/bin/ppthtml';
	

Zip might be interesing too, tar, rar etc, at the moment, word and ppt work fine, excel don't (mime type?) and zip+tar etc to do. Done Zip, Tgz, PDF Excel support, Vcards too, some changes, doesn't seem that mail clients send correct mime tyep.

Fortune cookies.

I like the UNIX fortune command that displays quotes and adages at random and thought it'd be nice to have it on the login screen. To make this work, let's apt-get the packages, again supporting as many languages as possible:

      apt-get install fortune fortunes fortunes-es fortunes-eo-ascii \
       fortunes-fr fortunes-it fortunes-pl fortune-zh fortunes-br \
       fortunes-de fortunes-cs fortunes-ga
    

To make it appear, edit the file /etc/imp3/motd.php and add the following at the end of the file but before the closing '?>':

      echo '<hr/><pre align="center" class="light"">';
      system('/usr/games/fortune');
      echo '</pre>';
    

Customising application menus

It's handy for the user if all the available Horde applications appear on the menus when in each of them, making navigation easier. You do this by editing the conf.php for each of the applications (/etc/imp3/conf.php, /etc/nag/conf.php, etc). An example entry for IMP is:

      $conf['menu']['apps'] = array('turba','kronolith','nag','mnemo');
    

And finally you need to make all the applications active and also change their names if you so desire, you do this by editing the application specific files in /etc/horde2/registry.d/, for example, for IMP you edit imp.php and change the entry to:

      $this->applications['imp'] = array(
        'fileroot' => '/usr/share/horde2/imp',
        'webroot' => $this->applications['horde']['webroot'] . '/imp',
        'icon' => '/horde2/imp/graphics/imp.gif',
        'name' => _("Slackmail"),
        'allow_guests' => true,
        'status' => 'active',
        'show' => true
      );
    

The key lines above being 'name' and 'staus'.

And finally: disabling the test pages

As a measure of security I then disabled the test pages for IMP and Horde as they let on important server information to the world at large:

      chmod o-r,g-r /usr/share/horde2/test.php
      chmod o-r,g-r /usr/share/horde2/imp/test.php
    

Pending IMP stuff to sort out

  1. At the moment the return-path and sender fields are set to 'www-data@mango.psand.net' whereas I think they should be the sender's email address. Not worked out how to do this yet, although I know it's possible. I thought it was done by correctly setting up an identity in IMP, but I've done that to no avail.
  2. IMP supports VFS for storage of attachments on the server. This to me sounds good, it's something that needs to be set-up. There's an option in conf.php for IMP ($conf['compose']['use_vfs'] = false;) but I think something also needs to be done in Horde for this.

References


Valid XHTML 1.1!

Home


Authors: Mike Harris, Psand.net
Version: $Revision: 1.1 $ (updated $Date: 2005/03/12 17:25:36 $)

GNU Project Free Documentation Licence
Article copyright (c) 2004 Psand Limited. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".