Mail Server Setup With Exim, MySQL, Cyrus-Imapd, Horde Webmail On Centos 5.1 - Page 2


Configure Apache

  • Enable virtual hosting and create default virtualhost, edit /etc/httpd/conf/httpd.conf and add at the end
    NameVirtualHost *:80
    <VirtualHost *:80>
        ServerAdmin [email protected]
  • Create the virtual host for horde webmail add this under the above
    <VirtualHost *:80>
            DocumentRoot /usr/share/horde
            ErrorLog logs/mail-error_log
            CustomLog logs/mail-access_log common
  • Enable horde security settings edit the file /etc/httpd/conf.d/horde.conf and set as below
    #Alias /horde /usr/share/horde
    <Directory /usr/share/horde>
        Options +FollowSymLinks
        php_admin_flag safe_mode off
        php_admin_flag magic_quotes_runtime off
        php_flag session.use_trans_sid off
        php_flag session.auto_start off
        php_admin_flag file_uploads on
        #php_admin_flag allow_url_fopen on
        php_value post_max_size 20M
        php_value upload_max_filesize 10M
        php_admin_value open_basedir "/usr/share/horde:/usr/share/horde/config:/usr/share/pear:/tmp"
        php_admin_flag register_globals off
    <Directory /usr/share/horde/config>
        Order Deny,Allow
        Deny from all
    <DirectoryMatch "^/usr/share/horde/(.*/)?(config|lib|locale|po|scripts|templates)/(.*)?">
        Order Deny,Allow
        Deny from all
  • Increase PHP memory limit edit /etc/php.ini and change to below
    memory_limit = 64M
  • Enable horde under SSL edit /etc/httpd/conf.d/ssl.conf and add the following to the default virtualhost between the <VirtualHost _default_:443><VirtualHost> tags
    DocumentRoot /usr/share/horde


Configure Exim

  • Switch the MTA to exim

system-switch-mail (select exim)

Anti-virus / Sanesecurity Checks

  • Configure Exim (/etc/exim/exim.conf) to use clamav to scan incoming mail and reject virus infected email and image and pdf spam at smtp time
av_scanner = clamd:/var/run/clamav/clamd.sock


  • Configure the RBL's under acl_check_rcpt:
    drop    message       = REJECTED because $sender_host_address is in a black list
               dnslists      =
    drop    message       = REJECTED because $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text
               dnslists      =
    drop    message       = REJECTED because $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text
               dnslists      =


Anti Spam

  • If you want to reject messages from servers with no reverse dns add this under acl_check_rcpt:, it does have a exception list to which you can add domains where the acl should not be applied and trys to deliver a test message to sending address to verify if the sender is valid.
    drop  message   = REJECTED - We don't accept messages from hosts without reverse DNS
            log_message = No reverse DNS
            domains = ! lsearch;/etc/exim/checks_exempt_hosts
            !verify = reverse_host_lookup
            !verify = sender/callout=2m,defer_ok
            !condition =  ${if eq{$sender_verify_failure}{}}
  • To reject messages from clients that dont provide a HELO/EHLO add this to acl_check_rcpt:
    drop  message  = REFUSED - no HELO/EHLO greeting
            log_message = remote host did not present greeting
            condition = ${if def:sender_helo_name {false}{true}}
  • You can rate limit the connections to your server as well add this to acl_check_connect: to do so (read the exim docs on the parameters if you want to fine tune it for your site)
    deny ratelimit = 250 / 15m / strict
           message = You can only send $sender_rate per $sender_rate_period
           log_message = RATE: $sender_rate/$sender_rate_period (max $sender_rate_limit)
  • Stop rogue spam bots from trashing your machine
    smtp_accept_max_nonmail = 30
    smtp_max_unknown_commands = 1
  • Don't advertise pipelining
    pipelining_advertise_hosts = 
  • Enable Spamassassin checks
    spamd_address = /var/run/spamassassin/spamd.sock
  • Reject all messages with score above 6 at smtp time. (acl_check_data)
    accept  condition  = ${if >={$message_size}{100000} {1}}
            add_header = X-Spam-Note: SpamAssassin run bypassed due to message size
      warn    spam       = nobody/defer_ok
            add_header = X-Spam-Flag: YES
      accept  condition  = ${if !def:spam_score_int {1}}
            add_header = X-Spam-Note: SpamAssassin invocation failed
      warn    add_header = X-Spam-Score: $spam_score ($spam_bar)\n\
    #       X-Spam-Report: $spam_report
      drop    condition = ${if >{$spam_score_int}{60} {1}}
            message   = Your message scored $spam_score SpamAssassin point. Report follows:\n\


Mail routing

  • Enable access to Mysql database
    hide mysql_servers = localhost/horde/horde/hordepassword
  • Modify the local delivery router to deliver to cyrus but verify the email address of user before delivery (in routers section of exim.conf)
      driver = accept
      local_parts = ${lookup mysql {SELECT REPLACE(user_uid,'${quote_mysql:@$domain}','') \
             as user FROM horde_users WHERE user_uid='${quote_mysql:$local_part@$domain}'}{$value}}
      transport = local_delivery
      cannot_route_message = Unknown user
  • Create a transport to deliver to cyrus via lmtp socket
      driver = lmtp
      socket = /var/lib/imap/socket/lmtp
      batch_max = 50
      user = cyrus


SMTP Authentication

  • Add the following to the authentication section of /etc/exim/exim.conf
      driver = plaintext
      public_name = PLAIN
      server_prompts = :
      server_set_id = $2
      server_condition = ${if saslauthd{{$2}{$3}{pop}}{1}{0}}
      server_advertise_condition = true
      driver = plaintext
      public_name = LOGIN
      server_prompts = "Username:: : Password::"
      server_condition = ${if saslauthd{{$1}{$2}{pop}}{1}{0}}
      server_set_id = $1
      server_advertise_condition = true


Full sample configuration

Download the full configuration file here.


Configure Mysql

  • Disable TCP networking edit /etc/my.cnf and the following in the mysqld section
  • Set root password

    /usr/bin/mysqladmin -u root password 'new-password'
    /usr/bin/mysqladmin -u root -h your_host_name password 'new-password' -p


Configure Horde

  • Edit the sql file and change the mysql password for the horde user

    cp /usr/share/horde/scripts/sql/create.mysql.sql .
    vi create.mysql.sql

    REPLACE INTO user (host, user, password)
        VALUES (
    -- IMPORTANT: Change this password!
  • Create the user and populate the horde database

    mysql -p < create.mysql.sql

  • Create the tables for turba (Address book)

    mysql -p horde < /usr/share/horde/turba/scripts/sql/turba_objects.mysql.sql

  • Create the tables for kronolith (calendering)

    mysql -p horde < /usr/share/horde/kronolith/scripts/sql/kronolith.mysql.sql


Horde Configuration

  • Create horde base configuration /usr/share/horde/config/conf.php
    $conf['debug_level'] = E_ALL;
    $conf['max_exec_time'] = 0;
    $conf['compress_pages'] = true;
    $conf['umask'] = 077;
    $conf['use_ssl'] = 2;
    $conf['server']['name'] = $_SERVER['SERVER_NAME'];
    $conf['server']['port'] = $_SERVER['SERVER_PORT'];
    $conf['session']['name'] = 'Horde';
    $conf['session']['use_only_cookies'] = true;
    $conf['session']['cache_limiter'] = 'nocache';
    $conf['session']['timeout'] = 0;
    $conf['cookie']['domain'] = $_SERVER['SERVER_NAME'];
    $conf['cookie']['path'] = '/';
    $conf['sql']['username'] = 'horde';
    $conf['sql']['password'] = 'hordepassword';
    $conf['sql']['socket'] = '/var/lib/mysql/mysql.sock';
    $conf['sql']['protocol'] = 'unix';
    $conf['sql']['database'] = 'horde';
    $conf['sql']['charset'] = 'iso-8859-1';
    $conf['sql']['phptype'] = 'mysqli';
    $conf['auth']['admins'] = array('Administrator', '[email protected]');
    $conf['auth']['checkip'] = true;
    $conf['auth']['checkbrowser'] = true;
    $conf['auth']['alternate_login'] = false;
    $conf['auth']['redirect_on_logout'] = false;
    $conf['auth']['params']['driverconfig'] = 'horde';
    $conf['auth']['params']['table'] = 'horde_users';
    $conf['auth']['params']['username_field'] = 'user_uid';
    $conf['auth']['params']['password_field'] = 'user_pass';
    $conf['auth']['params']['encryption'] = 'md5-hex';
    $conf['auth']['params']['show_encryption'] = false;
    $conf['auth']['driver'] = 'sql';
    $conf['signup']['allow'] = false;
    $conf['log']['priority'] = PEAR_LOG_NOTICE;
    $conf['log']['ident'] = 'HORDE';
    $conf['log']['params'] = array();
    $conf['log']['name'] = '/tmp/horde.log';
    $conf['log']['params']['append'] = true;
    $conf['log']['type'] = 'file';
    $conf['log']['enabled'] = true;
    $conf['log_accesskeys'] = false;
    $conf['prefs']['params']['driverconfig'] = 'horde';
    $conf['prefs']['driver'] = 'sql';
    $conf['datatree']['params']['driverconfig'] = 'horde';
    $conf['datatree']['driver'] = 'sql';
    $conf['group']['driver'] = 'datatree';
    $conf['cache']['default_lifetime'] = 1800;
    $conf['cache']['params']['dir'] = Horde::getTempDir();
    $conf['cache']['params']['gc'] = 86400;
    $conf['cache']['driver'] = 'file';
    $conf['token']['driver'] = 'none';
    $conf['mailer']['params']['auth'] = '0';
    $conf['mailer']['type'] = 'smtp';
    $conf['vfs']['params']['driverconfig'] = 'horde';
    $conf['vfs']['type'] = 'sql';
    $conf['sessionhandler']['params']['persistent'] = false;
    $conf['sessionhandler']['params']['rowlocking'] = true;
    $conf['sessionhandler']['params']['socket'] = '/var/lib/mysql/mysql.sock';
    $conf['sessionhandler']['params']['protocol'] = 'unix';
    $conf['sessionhandler']['params']['hostspec'] = 'localhost';
    $conf['sessionhandler']['params']['username'] = 'horde';
    $conf['sessionhandler']['params']['password'] = 'hordepassword';
    $conf['sessionhandler']['params']['database'] = 'horde';
    $conf['sessionhandler']['type'] = 'mysql';
    $conf['problems']['email'] = '[email protected]';
    $conf['problems']['maildomain'] = '';
    $conf['problems']['tickets'] = false;
    $conf['menu']['apps'] = array();
    $conf['menu']['always'] = true;
    $conf['menu']['links']['help'] = 'authenticated';
    $conf['menu']['links']['help_about'] = true;
    $conf['menu']['links']['options'] = 'authenticated';
    $conf['menu']['links']['problem'] = 'never';
    $conf['menu']['links']['login'] = 'all';
    $conf['menu']['links']['logout'] = 'authenticated';
    $conf['hooks']['permsdenied'] = false;
    $conf['hooks']['username'] = false;
    $conf['hooks']['preauthenticate'] = false;
    $conf['hooks']['postauthenticate'] = false;
    $conf['hooks']['authldap'] = false;
    $conf['portal']['fixed_blocks'] = array();
    $conf['accounts']['driver'] = 'null';
    $conf['imsp']['enabled'] = false;
    $conf['kolab']['enabled'] = false;
  • Set horde preferences to make web mail the default application on logging in. Edit the file /usr/share/horde/config/prefs.php and modify $_prefs['initial_application'] to look as below
    $_prefs['initial_application'] = array(
        'value' => 'imp',
        'locked' => true,
        'shared' => true,
        'type' => 'select',
        'desc' => sprintf(_("What application should %s display after login?"), $GLOBALS['registry']->get('name'))
  • Make horde work from within the default root of the web servers, edit /usr/share/horde/config/registry.php and modify $this?applications['horde'] as below
    $this->applications['horde'] = array(
        'fileroot' => dirname(__FILE__) . '/..',
        'webroot' => '',
        'initial_page' => 'login.php',
        'name' => _("Horde"),
        'status' => 'active',
        'templates' => dirname(__FILE__) . '/../templates',
        'provides' => 'horde'
Share this page:

3 Comment(s)

Add comment



I believe the correct blacklist for is:

not xen.  xen is the virtual host hypervisor thingy.  If you use the incorrect host name for that DNSBL, spamhaus will give you an answer for every query.  That means you will reject every single IP address.

Other than that, excellent article!


Well sported, it has been fixed.


The full configuration file of Exim listed in your how to is  in .gz format and is not readable after unzipping.Pls recheck  whether the file is in correct format.Other wise how can i open in a readable format??

Thank you