• 1 min read
  • I can't seem to find this on Beatport or iTunes to buy :(

  • 1 min read
  • Sometimes, I feel like people give Java a bad rap. It's a language that's in demand out on the field and after using it for my school classes, I have found that it is easy to program in, it performs very well and your code is portable/cross-platform. Sounds great, right? At first glance Java magically turns everything that's hard to do into something easy.

    But then I'll use Java-based software and somehow it manages to consistently be extremely complex to setup/configure (ie, tomcat+webapps), to have horrible looking UIs (ie, LimeWire, FreeNode, OpenOffice) and often it consumes lots of resources needlessly (ie, OpenOffice, LimeWire). I mean seriously, you can get a PHP or Python-enabled webserver up and running in under 2 commands on RHEL and then you just need to create a single script file to start serving pages. The Java community really needs to spend some time working on developer/user experience in my opinion.

  • 4 min read
  • I've always cared a lot about user experience because I find that no matter how great a single piece or a collection of software is, it is the user experience that shapes your impression of that software. That said, I was cleaning up my (very, very messy) desktop and came across an old backup of a Fedora installation. There was a file on my desktop from July 2008 that I had completely forgotten about... I had collected my thoughts at the time on how Linux-based distributions could be improved to make the user experience better. It's really neat to see how many of these have been implemented in only 3 short years:

    my apologies for the messy read, I tend to write my notes in Wiki format and I don't feel like copy/pasteing

  • 's all over the place ;)

    * The Linux installation+boot process
      * Installers must try to recognize an existing Linux installation's boot
        configuration and add theirs to it, not overwrite the previous one.
      * Be able to partition (read: resize) other filesystems intelligently and
      * Provide installation profiles. Stop fighting over what packages or
        configurations to use and realize that a server, an enterprise and the
        typical user all have different sets of expectations and needs.
      * GRUB should have an extendable plug-in system where distributions could
        plug-in modules to have it suggest which partitions to boot from (ie
        distribution auto-find)
      * Graphical bootup: X in initramfs. Ubuntu does this already, it's an excellent
        idea and gives the user a better overall experience.
    * Standardizing the Desktop
      * User accounts
        * Unix names are confusing to users. Have the system map metanames to Unix
          names so that people can login with e.g. "firewing" or "Stewart Adam"
        * Allow the administrator to create user groups and define their privileges.
          User accounts belong to one or more groups which defines what they can do.
          * User control is easy and at the same time they can be given needed
            privileges (software updates, mounting drives, etc) without having to
            know the root password.
      * Unified package management. Create a standard for both package managers and
        packaging. This enables large, cross-distro compatible repositories that
        benefit the users.

    * User experience
      * Prompt the user for backups once a week. Include a don't show me again
      * Why can't we configure tapping on a per-user basis again? Right, xorg.conf.
    * Developers
      * Need to accept and handle user feedback. Although Linux is used by a lot of
        developers, most of the users are non-developers users. It would make
        sense to prioritize what they have to say.

    * Kernel
      * If a device is present but isn't supported, provide a signal so that the
        desktop environment can present a dialog explaining the problem and showing
        the user what they can do to help.
        * More specifically, reporting the device IDs and collecting common log
      * Create "FooKits" for helping monitor and solve common problems. Power usage,
        kernel oopses, SELinux, etc.
      * Reload parts of the kernel without rebooting (just improve kmem)

    * Documentation
      * Don't leave users out in the cold. They shouldn't have to do a day of
        research to get the OS installed or to perform simple tasks. Provide
        tooltips and help buttons inside programs.
      * Dumbing down doesn't solve much. The best type of documentation is easy
        to understand but contains technical information at the same time.
    * Other
      * Interfaces need to be somewhat standardized and resemble each other in
        nature. They overall goal is that programs should be intuitive -
        Documentation should accompany a program, but the interface should be
        intuitive enough that users shouldn't have to read it to get started.
      * Something nice for the help menu layout:
        - Documentation
        - Check for Updates (this would use the standardized package manager)
        - Report a bug
        - Help translate this program
        - About this program
      * Synchronize user information (ie, UID/GIDs) between various distributions.
      * There needs to be an easy communication channel between developers/
        packagers and users so that they are encouraged to help out. Testing and
        providing feedback and bug reporting and bug sorting/solving is not hard but
        goes a long way in helping the developers troubleshoot problems.

  • 15 min read
  • This how-to will show you how to configure:

    • An Apache 2 web server using virtual hosts
    • The ITK MPM allows each virtual host to serve requests as its own user/group
    • mod_ssl to serve pages over the secure HTTP (HTTPS) protocol
    • mod_security to help prevents everything from SQL injections to data leaks
    • mod_php for PHP scripts along with mod_suhosin to help protect mitigate risks from known and unknown flaws in PHP scripts

    Rebuilding httpd for ITK

    About privilege separation

    By default, the Apache web server runs as the 'apache' user. This is good because a successful attack on the web server will only cause limited damage to the system, as they do not have root access. However on a shared servers which hosts multiple websites, an attack can still be very dangerous because the 'apache' user is used by all of the websites. Thus, an attack on one website can potentially grant access to any other websites that are being hosted on the system!

    Privilege separation is a technique that can be used to mitigate the risk of an attack against a shared hosting server. By allowing webpages to be served by different users, each host can be assigned its own user and so the damage from a site hack will be limited to just that account and not all of the accounts hosted on the system. We will be using the ITK Apache MPM to achieve this.

    Rebuild process

    Unfortunately, the ITK MPM is not included in the stock httpd distribution. Fortunately, it is relatively easy to add it. Run this as your regular user to install the httpd source RPM and download the ITK patch sources:

    yumdownloader --source httpd
    rpm -i httpd*.src.rpm
    rm httpd*.src.rpm
    cd ~/rpmbuild/SOURCES
    wget http://mpm-itk.sesse.net/apache2.2-mpm-itk-2.2.17-01/{02-rename-prefork-to-itk.patch,03-add-mpm-to-build-system.patch,04-correct-output-makefile-location.patch,05-add-copyright.patch,06-hook-just-after-merging-perdir-config.patch,07-base-functionality.patch,08-max-clients-per-vhost.patch,09-capabilities.patch,10-nice.patch,11-fix-htaccess-reads-for-persistent-connections.patch}

    With that done, let's make a few quick modifications to the RPM spec file located at ~/rpmbuild/SPECS/httpd.spec.

    We will first need to add the ITK patches. Find the last patch line in the RPM spec file, for example, Patch202: httpd-2.2.3-deflate2215.patch, then add after it:

    # ITK MPM
    Patch802: 02-rename-prefork-to-itk.patch
    Patch803: 03-add-mpm-to-build-system.patch
    Patch804: 04-correct-output-makefile-location.patch
    Patch805: 05-add-copyright.patch
    Patch806: 06-hook-just-after-merging-perdir-config.patch
    Patch807: 07-base-functionality.patch
    Patch808: 08-max-clients-per-vhost.patch
    Patch809: 09-capabilities.patch
    Patch810: 10-nice.patch

    Now in the %pre section - after the Red Hat patches are applied - add the following lines:

    # ITK MPM
    mkdir server/mpm/experimental/itk/
    cp -d --preserve=all server/mpm/prefork/* server/mpm/experimental/itk/
    mv server/mpm/experimental/itk/prefork.c server/mpm/experimental/itk/itk.c
    %patch802 -p1 -b .mpm02
    %patch803 -p1 -b .mpm03
    %patch804 -p1 -b .mpm04
    %patch805 -p1 -b .mpm05
    %patch806 -p1 -b .mpm06
    %patch807 -p1 -b .mpm07
    %patch808 -p1 -b .mpm08
    %patch809 -p1 -b .mpm09
    %patch810 -p1 -b .mpm10

    This will copy the (fully Red Hat-patched) prefork MPM code into a new folder and then apply the ITK patches.

    In the %build section of the spec file, you will see these lines:

    # For the other MPMs, just build httpd and no optional modules
    mpmbuild worker --enable-modules=none
    mpmbuild event --enable-modules=none

    Following the same format, add a line for the ITK MPM:

    mpmbuild itk --enable-modules=none

    Similarly, we find in the %install section:

    install -m 755 worker/httpd $RPM_BUILD_ROOT%{_sbindir}/httpd.worker
    install -m 755 event/httpd $RPM_BUILD_ROOT%{_sbindir}/httpd.event

    Add a line for the ITK MPM:

    install -m 755 itk/httpd $RPM_BUILD_ROOT%{_sbindir}/httpd.itk

    Last of all, in the %check section there is another section were we need to add in the ITK MPM:

    # Verify that the same modules were built into the httpd binaries
    ./prefork/httpd -l | grep -v prefork > prefork.mods
    for mpm in worker; do

    Change the middle line to read:

    for mpm in worker event itk

    We are now ready to rebuild our ITK-enabled httpd package.

    cd ~/rpmbuild/SPECS
    rpmbuild -ba httpd.spec

    Install the dependencies listed and then re-run the rpmbuild again if necessary. After the build has finished, install your new RPMs:

    rpm -Uhv ../RPMS/[arch]/{httpd,mod_ssl}-2*.rpm

    Remember to replace [arch] in the second to last command with the appropriate value (most probably i686 for 32-bit machines or x86_64 for 64-bit machines). The last step is to set the httpd worker to the ITK binary by editing /etc/sysconfig/httpd and adding after the line #HTTPD=/usr/sbin/httpd.worker:


    Installing add-on modules and additional software

    Let's install PHP, AWStats and a few other useful add-ons:

    yum install mod_security php php-{suhosin,mysql,mcrypt,mhash,gd} awstats

    Keep in mind that by default, mod_security's rules are very restrictive and you will almost certainly need to tweak them. Be sure to test thoroughly before enabling mod_security on your live server. You can modify the mod_security settings by editing the rule files in /etc/httpd/modsecurity.d.

    Configuring the web server

    You will find in a stock installation, no indexes are permitted at all and that visiting localhost displays the standard CentOS test page. This can be changed by editing /etc/httpd/conf.d/welcome.conf and comment out the entire LocationMatch section:

    #    Options -Indexes
    #    ErrorDocument 403 /error/noindex.html

    Next, let's add a small configuration file with some custom settings. Below we will try and prevent information leaks by restricting access to backup files (files ending with a "~"), .sql and .inc plaintext files which could potentially reveal critical information about how a site functions. We will also add a one-line include directive so that all files in the virtual hosts configuration directory get included as well.

    mkdir /etc/httpd/vhosts.d
    cat << EOF > /etc/httpd/conf.d/z_custom.conf
    # named with a z_ prefix to ensure this is parsed last.

    # Restrict access to sensitive file extensions that shouldn't be read via a
    # browser: *~ for temp/backup files, *.sql, *.inc as it isn't registered as
    # a php file.

      Order allow,deny
      Deny from all

    # Include our VirtualHost configurations
    Include vhosts.d/*.conf

    The following will initialize a template VirtualHost configuration that can be used by a script to automate the installation of new VirtualHosts:

    cat << EOF > /etc/httpd/vhosts.d/sample.conf
    #    ServerName \$WEBDOMAIN
    #    ServerAdmin webmaster@\$DOMAIN
    #    ServerAlias \$DOMAIN_ALIASES
    #    DocumentRoot /home/\$USER/web/public_html
    #    ErrorLog /home/\$USER/web/logs/error_log
    #    CustomLog /home/\$USER/web/logs/access_log combined
    #    php_admin_flag log_errors on
    #    php_admin_value error_log /home/\$USER/web/php_error_log
    #        php_admin_value session.save_path "/var/lib/php/session/\$USER"
    #        AssignUserId \$USER \$USER

    #        Options FollowSymLinks
    #        AllowOverride FileInfo AuthConfig Limit Indexes Options



    Similarly, we will setup a awstats template that can create configuration via a script:

    cat << EOF > /etc/awstats/awstats.model_custom.conf
    LogSeparator=" "



    NotPageList="css js class gif jpg jpeg png bmp ico rss xml swf"
    ValidHTTPCodes="200 304"
    ValidSMTPCodes="1 250"
    MaxNbOfDomain = 10
    MinHitDomain  = 1
    MaxNbOfHostsShown = 10
    MinHitHost    = 1
    MaxNbOfLoginShown = 10
    MinHitLogin   = 1
    MaxNbOfRobotShown = 10
    MinHitRobot   = 1
    MaxNbOfPageShown = 10
    MinHitFile    = 1
    MaxNbOfOsShown = 10
    MinHitOs      = 1
    MaxNbOfBrowsersShown = 10
    MinHitBrowser = 1
    MaxNbOfScreenSizesShown = 5
    MinHitScreenSize = 1
    MaxNbOfWindowSizesShown = 5
    MinHitWindowSize = 1
    MaxNbOfRefererShown = 10
    MinHitRefer   = 1
    MaxNbOfKeyphrasesShown = 10
    MinHitKeyphrase = 1
    MaxNbOfKeywordsShown = 10
    MinHitKeyword = 1
    MaxNbOfEMailsShown = 20
    MinHitEMail   = 1
    BarWidth   = 260
    BarHeight  = 90
    color_Background="FFFFFF" # Background color for main page (Default = "FFFFFF")
    color_TableBGTitle="CCCCDD" # Background color for table title (Default = "CCCCDD")
    color_TableTitle="000000" # Table title font color (Default = "000000")
    color_TableBG="CCCCDD" # Background color for table (Default = "CCCCDD")
    color_TableRowTitle="FFFFFF" # Table row title font color (Default = "FFFFFF")
    color_TableBGRowTitle="ECECEC" # Background color for row title (Default = "ECECEC")
    color_TableBorder="ECECEC" # Table border color (Default = "ECECEC")
    color_text="000000" # Color of text (Default = "000000")
    color_textpercent="606060" # Color of text for percent values (Default = "606060")
    color_titletext="000000" # Color of text title within colored Title Rows (Default = "000000")
    color_weekend="EAEAEA" # Color for week-end days (Default = "EAEAEA")
    color_link="0011BB" # Color of HTML links (Default = "0011BB")
    color_hover="605040" # Color of HTML on-mouseover links (Default = "605040")
    color_u="FFAA66" # Background color for number of unique visitors (Default = "FFAA66")
    color_v="F4F090" # Background color for number of visites (Default = "F4F090")
    color_p="4477DD" # Background color for number of pages (Default = "4477DD")
    color_h="66DDEE" # Background color for number of hits (Default = "66DDEE")
    color_k="2EA495" # Background color for number of bytes (Default = "2EA495")
    color_s="8888DD" # Background color for number of search (Default = "8888DD")
    color_e="CEC2E8" # Background color for number of entry pages (Default = "CEC2E8")
    color_x="C1B2E2" # Background color for number of exit pages (Default = "C1B2E2")
    LoadPlugin="geoip GEOIP_STANDARD /var/lib/GeoIP/GeoIP.dat"

    (For those wondering, this template was derived by removing all comments from the stock awstats model configuration at /etc/awstats/awstats.model.conf and then substituting important values such as SiteDomain with variables so that can be easily changed via sed)

    Because the web server will be using the ITK MPM, each site's requests will be run under its respective owner and group. This causes problems with the standard PHP session setup, which uses one directory owned by "apache" for all session information. To solve this, a session directory will need to be created for each user. Let's create the default one for the apache user now:

    chown root.apache /var/lib/php/session
    chmod 0771 /var/lib/php/session
    mkdir -m 0770 /var/lib/php/session/apache
    chown root.apache /var/lib/php/session/apache

    You are now ready to add VirtualHosts for your domains. See Adding a new web hosting account section of Administering the server below for more information on how to do so.

    The last step is to add the firewall port exceptions for http/https and start Apache:

    chkconfig httpd on
    service httpd start
    iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
    iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT
    service iptables save

    Suhosin PHP extension

    We will now be configuring /etc/php.d/suhosin.ini. Because suhosin can interfere with some site's functionality, it is best to enable it in simulation mode so that errors are logged and you can configure suhosin accordingly:

    suhosin.simulation = On

    Limit the maximum PHP memory_limit that scripts can set:

    suhosin.memory_limit = 512M

    Lastly, disable session encryption (breaks roundcubemail if enabled):

    suhosin.session.encrypt = Off

    Optional add-on: roundcubemail

    Let's rebuild roundcubemail 0.3 for CentOS 5:

    su - myusername
    cd ~/rebuilds
    fedpkg clone -a roundcubemail
    cd roundcubemail
    fedpkg switch-branch el6

    Edit the spec file roundcubemail.specand comment the line Requires: php-pear-Mail-mimeDecode so that it reads #Requires: php-pear-Mail-mimeDecode. This is the only change required for CentOS 5, so let's build the package now:

    fedpkg local
    yum install --nogpgcheck /home/myusername/rebuilds/roundcubemail/noarch/roundcubemail*.rpm

    Now it is time to configure roundcubemail. In the file /etc/httpd/conf.d/roundcubemail.conf, add after the line :

                AssignUserId apache apache

    As well, by default roundcubemail denies access to any non-localhost clients. To change this, comment out Deny from all and add Allow from all:

            Order Deny,Allow
            #Deny from all
            Allow from
            Allow from all

    We will also need to create the roundcubemail database. If you have not setup your database server, follow the database tutorial now.

    CREATE DATABASE roundcubemail;
    USE roundcubemail;
    source /usr/share/doc/roundcubemail-0.3.1/SQL/mysql.initial.sql
    GRANT ALL PRIVILEGES ON roundcubemail.* TO 'roundcubemail'@'localhost' IDENTIFIED BY 'random-password';

    Then configure /etc/roundcubemail accordingly. You should now be able to access the webmail portal at http://www.yourdomain.com/roundcubemail.

    But I want PHP 5.2!

    PHP 5.2 is available in the CentOS 5 Testing repository. Certain PHP add-ons, such as php-suhosin, have not been rebuilt so you will need to do that manually.

    rpm -e php-suhosin
    yum --enablerepo=c5-testing update php\*

    Now rebuild and reinstall php-suhosin:

    yumdownloader --source php-suhosin
    rpmbuild --rebuild php-suhosin-*.src.rpm

    Now install the resulting php-suhosin binary RPM and then move the configuration file (/etc/php.d/suhosin.ini.rpmsave) back into place.

    But I want PHP 5.3!

    PHP 5.3 is available as of CentOS 5.6 the php53 package. Certain PHP add-ons, such as php-suhosin and php-mcrypt have not been rebuilt so you will need to do that manually. This is a bit tricky as you will need to rebuild the php-extras SRPM among others; I'll leave you to figure out the details. See the instructions above for PHP 5.2 for a rough workflow using php-suhosin as an example.

    Some final words

    Two 3rd party MPM modules satisfied my need privilege separation in this setup, the ITK MPM and the Peruser MPM. Each has its own advantages and disadvantages.

    ITK's approach is to accept a request, determine which virtual host the request belongs to, fork, set the UID and GID of the fork, serve the request and then to terminate the fork after the request has been completed allowing it to fork again later. Because ITK is very simple in nature and functions similarly to the traditional prefork MPM, it is more compatible with modules like mod_ssl, mod_php and mod_python. That said, it is also slower than because of the fork/kill overhead associated with each request.

    Peruser, on the other hand, starts a pool of processes that fork only on startup. There are multiplexers than accept requests, determine which virtual host the request belongs to, and then forwards the request to the appropriate process from the pool. This approach incurs practically no overhead as the process from the pool has already been forked and had its UID/GID changed. Peruser is therefore much faster than ITK; its performance is nearly almost on par with that of the traditional prefork MPM! The downfall of Peruser is that because of this connection passing from the multiplexer to a preforked process, it is more complex and has had a long history of bugs and incompatibilities. Experimental support for mod_ssl was only recently added, and upstream development still seems to have a number of bugs to work out. In addition, because Peruser requires at least one process per virtual host for the pool and then requires multiplexers to handle incoming requests, it is much more resource intensive that the ITK MPM, where a process can dynamically handle a request from any virtual host. For these reasons, I favoured ITK over Peruser.

    Keep in mind that ITK does have one security flaw. The request headers must be processed in order to determine which virtual host that request belongs to, and therefore which UID/GID ITK would need to switch to. Because of this, all of the header processing is performed as root (albeit with restricted POSIX capabilities). Should someone be able to take advantage of a flaw in Apache or another module (e.g. mod_ssl), the server could theoretically be rooted.

    There is an emphasis on theoretically because an attacker would need to be extremely clever (or lucky) to successfully root the server via a flaw in the request processing given the restricted POSIX capabilities. In most cases, that specific apache process would just crash and life goes on. All in all, I still consider the ITK MPM to be more secure than the standard prefork MPM because a much more likely attack on the server is a hacker breaking in through a flaw in one of the hosted website's scripts (e.g. an outdated CMS installation). In this scenario the ITK MPM protects you completely; the potential damage from the attacker is essentially limited to that user's home directory, since each user's scripts run as their own UID/GID. They do not get access to any other user's scripts, nor to any other user's emails.

    Administering the server

    Setting up the scripts

    The following code will setup the "web_domain_add" script which can be used to create the initial website configurations for a hosting users on your server:

    cat << EOF > /root/bin/web_domain_add

    if [ -z \$domain ] || [ "\$username" == "-h" ];then
      echo "Usage: \$1 user domain [alias1] [alias2] [...]"
      exit 1

    for alias in "\$@";do
      aliases="\$aliases \$alias www.\$alias "

    cat /etc/httpd/vhosts.d/sample.conf | \
      sed "s/\\\$WEBDOMAIN/www.\${domain}/g" | \
      sed "s/\\\$DOMAIN_ALIASES/\${aliases}/g" | \
      sed "s/\\\$USER/\${username}/g" | \
      sed "s/\\\$DOMAIN/\${domain}/g" | \
      sed "s/^#//g" \
      > "/etc/httpd/vhosts.d/\${username}.conf"
    echo "*** Writing VHost configuration /etc/httpd/vhosts.d/\${username}.conf"

    cat /etc/awstats/awstats.model_custom.conf | \
      sed "s/\\\$WEBDOMAIN/www.\${domain}/g" | \
      sed "s/\\\$DOMAIN_ALIASES/\${aliases}/g" | \
      sed "s/\\\$USER/\${username}/g" | \
      sed "s/\\\$DOMAIN/\${domain}/g" \
      > /etc/awstats/awstats.www.\${domain}.conf
    echo "*** Writing AWStats configuration /etc/awstats/awstats.www.\${domain}.conf"

    echo "*** Creating password for AWStats"
    htpasswd /home/awstats-htpasswd "\$username"

    echo "*** Done"
    chmod +x /root/bin/web_domain_add

    Adding a new web hosting account

    In order to create the new website configurations, you must also create a new system user that the VirtualHost will be mapped to via ITK. See the SSH+SFTP tutorial for details on how to add a new restricted system user. Once you have done so, you can run the web_domain_add script:

    /root/bin/web_domain_add system_username primarydomain.tld

    This will initialize a new configuration for the domain primarydomain.tld mapped to system_username. You may optionally specify as many domain aliases as needed, for example:

    /root/bin/web_domain_add exampleuser example.com example.net example.org

    www.example.com will be used as the primary domain, and the script will automatically alias example.com as well as www.example.net, example.net, www.example.org and example.org as domain aliases for www.example.com.
    Note: The script does not create any DNS entries for these domains! That task must be performed separately with your DNS provider.

    Resources and further reading

  • 17 min read
  • This how-to will show you how to configure:

    • A MySQL database to store information about email accounts, aliases (per-address or per-domain) and autoresponders
    • Postfix as your mail transfer agent (MTA) for SMTP
      • amavisd-new with clamav & spamassassin for automatic virus and spam filtering
      • Ability to define virtual users (via MySQL database) mapped to a real UID/GID or system users
      • Aliases generated automatically from the MySQL database, allowing one address to forward to another or all addresses on a domain to forward to another
      • Response handling email autoresponders
      • Dovecot as the local delivery agent (LDA) delivering mail to the corresponding user's mailbox
      • Dovecot as SASL authenticator
    • Dovecot for POP3 & IMAP
      • Dovecot LDA delivering mail as any system user (better security)
      • SASL authentication based on virtual user information stored in the MySQL database

    As this may seem like a bit complex at first, below is a simple overview of how the system works once put together:

    • When a user connects to the server to retrieve mail (POP3 or IMAP), Dovecot performs the SASL authentication and then connects the user to their message inbox.
    • When a user sends mail to the server destined for another domain (outgoing SMTP), Postfix calls on Dovecot to perform SASL authentication. If it succeeds, relay access is granted and the message is sent to the external mail server.
    • When an external user sends mail to the server destined for a domain handled by Postfix (incoming SMTP), Postfix checks the system and virtual user tables to ensure that the recipient is valid. If so, then it passes through a few verifications first (valid sender, virus filter, spam filter) and if all pass then the message is delivered to the corresponding user's inbox via Dovecot LDA.

    Before starting

    Please ensure that you have followed the instructions in the getting started guide here. This tutorial may require you to rebuild RPMs as well as generate SSL certificates and assumes you have the proper environment described in that guide already setup.

    If you have not setup the database server yet, please follow the database how-to first.

    Setting up the database

    Before installing dovecot or postfix, the MySQL database which will be used to authenticate all email clients needs to be initialized. Execute this SQL snippet:

    CREATE DATABASE mailconfig;
    USE mailconfig;
    CREATE TABLE `users` (
      `userid` varchar(128) NOT NULL,
      `domain` varchar(128) NOT NULL,
      `password` varchar(255) NOT NULL,
      `home` varchar(255) NOT NULL,
      `uid` int(11) NOT NULL,
      `gid` int(11) NOT NULL,
      PRIMARY KEY  (`userid`,`domain`)
    ) DEFAULT CHARSET=utf8 COMMENT='Stores information about active email accounts';

    CREATE TABLE `aliases` (
      `source` varchar(255) NOT NULL,
      `destination` varchar(255) NOT NULL,
      PRIMARY KEY  (`source`)
    ) DEFAULT CHARSET=utf8 COMMENT='Alias one address to another';

    CREATE TABLE `domain_aliases` (
      `source` varchar(128) NOT NULL,
      `destination` varchar(128) NOT NULL,
      PRIMARY KEY  (`source`)
    ) DEFAULT CHARSET=utf8 COMMENT='Alias all addresses in one domain to another';

    GRANT SELECT ON mailconfig.users TO 'mailconfig'@'localhost' IDENTIFIED BY 'random-password';
    GRANT SELECT ON mailconfig.aliases TO 'mailconfig'@'localhost' IDENTIFIED BY 'random-password';
    GRANT SELECT ON mailconfig.domain_aliases TO 'mailconfig'@'localhost' IDENTIFIED BY 'random-password';

    Replace random-password by a long squence of random characters. The mailconfig user will only be used by scripts, so you will not need to remember this password.

    Incoming mail: Dovecot

    Grab dovecot from the repositories:

    yum install dovecot

    Dovecot is now ready to be configured. Execute the following to configure the Dovecot to allow imap/pop3 access securely with compatibility for some older clients with broken protocol support:

    cat << EOF > /etc/dovecot.conf
    protocols = imap imaps pop3 pop3s

    # Log authentication failures

    # Enable me to debug authentication failures
    #verbose_ssl = yes

    # Maildir storage in [HOME]/[DOMAIN]/mail/[USER]
    mail_location = maildir:%h/mail/%d/%n
    umask = 0077 # 700 permissions

    # for compatability with some older clients
    pop3_uidl_format = %08Xu%08Xv
    imap_client_workarounds = delay-newmail outlook-idle netscape-eoh
    pop3_client_workarounds = outlook-no-nuls oe-ns-eoh

    # Increases performance
    maildir_copy_with_hardlinks = yes
    # Enable SSL/TLS
    ssl_disable = no
    ssl_cert_file = /etc/pki/dovecot/certs/dovecot.pem
    ssl_key_file = /etc/pki/dovecot/private/dovecot.pem
    # disable insecure ciphers
    ssl_cipher_list = ALL:!LOW:!SSLv2
    # Force STARTTLS on non-secure protocols; users *must* have either secure auth
    # (CRAM-MD5) or SSL enabled (or both)
    disable_plaintext_auth = yes
    login_process_per_connection = yes

    # Keep username case-sensitive (otherwise delivery to local users with uppercase letters fails
    auth_username_format = %u

    auth default {
      mechanisms = plain login cram-md5
      passdb sql {
        args = /etc/dovecot-mysql.conf
      # Indicate that we want to prefetch userdb information from the passdb
      userdb prefetch {
      # The userdb below is used only by deliver.
      userdb sql {
        args = /etc/dovecot-mysql.conf
      # Offer SASL auth services
      socket listen {
        client {
          path = /var/run/dovecot/auth-client
          mode = 0660
          user = dovecot
          group  = mail # Postfix running as this user
        master {
          path = /var/run/dovecot/auth-master
          mode = 0660
          user = dovecot
          group = mail # User running deliver
    protocol lda {
      postmaster_address = postmaster@yourdomain.com
      auth_socket_path = /var/run/dovecot/auth-master

    Once again, substitute yourdomain.com and the SSL certification/key file locations accordingly. You'll notice we reference the non-existent file /etc/dovecot-mysql.conf in this configuration. Let's create that now so Dovecot has access to the MySQL database:

    cat << EOF > /etc/dovecot-mysql.conf
    # Substitutions: %u = user@domain.com, %n = user, %d = domain.com, L prefix=lowercase
    driver = mysql
    connect = host=/var/lib/mysql/mysql.sock dbname=mailconfig user=mailconfig password=random-password

    # Password lookups with support for user information prefetching:
    password_query = SELECT concat(userid, '@', domain) AS user, password, home AS userdb_home, uid AS userdb_uid, gid AS userdb_gid  FROM users  WHERE userid = '%Ln' AND domain = '%Ld'

    # For deliver user info lookups:
    user_query = SELECT home, uid, gid  FROM users  WHERE userid = '%Ln' AND domain = '%Ld'
    chmod 600 /etc/dovecot-mysql.conf

    This creates dovecot's MySQL configuration, /etc/dovecot-mysql.conf and sets the correct permissions accordingly so that users can't go snooping for the MySQL credentials. Again, replace random-password with the correct password.

    Because the 'deliver' (Dovecot local delivery agent) executable run by Postfix has to deliver mail to different system users, it will need a setuid bit set so that Postfix can run deliver with root privileges, have it change to the appropriate user and then deliver mail to that user's inbox. We can do this easily with two commands:

    chown root.mail /usr/libexec/dovecot/deliver
    chmod 4750 /usr/libexec/dovecot/deliver

    Notice that because the last permission bit is "0", nobody except root or users in the mail group will be able to execute deliver, reducing the security risk introduced by using enabling setuid bit.

    Lastly, Dovecot needs to be started and exceptions need to be added to the firewall:

    chkconfig dovecot on
    service dovecot start
    iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 110 -j ACCEPT
    iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 143 -j ACCEPT
    iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 993 -j ACCEPT
    iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 995 -j ACCEPT
    service iptables save

    Outgoing mail: Postfix

    Normally we could just install Postfix directly from the repository, however the stock CentOS postfix is provided without MySQL support. We can easily enable this support by rebuilding the RPM:

    su - normaluser
    yumdownloader --source postfix
    rpm -i postfix*src*.rpm
    cd rpmbuild/SPECS
    sed -i.nomysql 's/%define MYSQL 0/%define MYSQL 1/' postfix.spec
    rpmbuild -ba postfix.spec

    Then install the generated Postfix RPM (rpm -Uhv ~/rpmbuild/RPMS/[arch]/postfix-[version].[arch].rpm)

    Next, we will configure Postfix to accept/relay mail:

    cat << EOF > /etc/postfix/main.cf
    # Basic settings

    # Domain/hostname information
    mydomain = yourdomain.com
    myhostname = mail.yourdomain.com

    # Domains that Postfix will use to deliver mail to system users
    mydestination = \$myhostname, localhost.\$mydomain, localhost

    # Trusted networks that are always granted relay access
    # You may remove if you are not going to use virtual machines
    # with libvirt (default/user networking).
    mynetworks =,

    # Listen on these IP addresses
    inet_interfaces = all

    # Enter an IP address here and uncomment to get lots of info logged when
    # connecting as a client from that IP address.
    # debug_peer_list = your.ip.address.here

    # File/folder locations
    command_directory = /usr/sbin
    config_directory = /etc/postfix
    daemon_directory = /usr/libexec/postfix
    html_directory = no
    manpage_directory = /usr/share/man
    sample_directory = /usr/share/doc/postfix-2.3.3/samples
    queue_directory = /var/spool/postfix
    readme_directory = /usr/share/doc/postfix-2.3.3/README_FILES

    mailq_path = /usr/bin/mailq.postfix
    newaliases_path = /usr/bin/newaliases.postfix
    sendmail_path = /usr/sbin/sendmail.postfix

    # Security & limits
    mail_owner = postfix
    setgid_group = postdrop
    header_size_limit = 51200

    # 30MB message size limit
    message_size_limit = 31457280
    # Must have 5*30MB free in order to accept mail
    queue_minfree = 157286400

    # Dovecot LDA & virtual users configuration
    dovecot_destination_recipient_limit = 1
    virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf
    virtual_alias_domains = mysql:/etc/postfix/mysql-virtual-alias-domains.cf
    virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
    virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
    virtual_transport = dovecot

    # Other settings in alphabetical order
    alias_maps = hash:/etc/aliases
    broken_sasl_auth_clients = yes
    parent_domain_matches_subdomains = no
    recipient_delimiter = +
    smtpd_banner = \$myhostname ESMTP \$mail_name: Unauthorized use of this server, for spam, bulk mail or other purposes, is not permitted.
    smtpd_client_connection_count_limit = 20
    smtpd_client_connection_rate_limit = 60
    smtpd_delay_reject = yes
    smtpd_helo_required = yes
    smtpd_helo_restrictions = permit_mynetworks, warn_if_reject, reject_non_fqdn_helo_hostname, reject_invalid_hostname
    smtpd_recipient_limit = 100
    smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_recipient, reject_unknown_recipient_domain, reject_unauth_destination
    smtpd_sasl_auth_enable = yes
    smtpd_sasl_path = /var/run/dovecot/auth-client
    smtpd_sasl_security_options = noanonymous
    smtpd_sasl_type = dovecot
    smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_sender, reject_unknown_sender_domain
    smtpd_tls_auth_only = no
    smtpd_tls_cert_file = /etc/pki/tls/certs/yourdomain.com.pem
    smtpd_tls_key_file = /etc/pki/tls/private/yourdomain.com.key
    smtpd_tls_loglevel = 1
    smtpd_tls_security_level = may
    smtpd_tls_session_cache_database = btree:/var/spool/postfix/smtpd_tls_cache
    smtpd_tls_session_cache_timeout = 3600s
    tls_random_source = dev:/dev/urandom
    unknown_local_recipient_reject_code = 550

    Be sure to substitute yourdomain.com for your actual domain name as well as set the smtpd_tls_cert_file and smtpd_tls_key_file configuration keys appropriately. You can use the cert.pem and private/localhost.key files that get autogenerated for the system, however it is recommended that you generate your own self-signed certificate so that the domain name matches (or even better, purchase validated ones). You may create a self-signed certificate and private key pair by running genkey --days 365 yourdomain.com and then following the on-screen instructions.

    The configuration above references several configuration files that do not exist yet, so we need to create them now. A description of what each file does is available in the comments.

    cat << EOF > mysql-virtual-alias-domains.cf
    # Generates a list of domain names that postfix should accept mail for, but
    # that have no real mailboxes (aliases only). See the virtual(5) manpage for
    # more information.

    # See the mysql_table(5) manpage for information on the format of this file
    # and the SQL query.

    user = mailconfig
    password = random-password
    hosts =
    dbname = mailconfig
    query = SELECT DISTINCT(source) FROM domain_aliases

    cat << EOF > mysql-virtual-alias-maps.cf
    # This file contains the SQL query for looking up virtual aliases. See the
    # virtual(5) manpage for more information. The virtual alias maps allows you
    # to forward messages from one address or from a catch-all address to another
    # address (or optionally, a script). This file also handles the domain forwarding
    # of addresses in one domain to another.

    # Caveat: One would expect true domain forwarding to map user accounts as well
    # as any existing alias on the source domain to the destination domain.
    # For example, consider the use case of virtual user me@domain1.com and alias
    # info@domain1.com -> me@domain1.com. After mapping domain2.com to domain1.com,
    # one would expect info@domain2.com to map to info@domain1.com which in turn
    # maps to me@domain1.com, so info@domain2.com -> me@domain1.com. Implementing
    # this type of mapping is rather complex. The commented portion of the query
    # below should handle this correctly, however the performance penalties of using
    # such a query have not been considered nor tested on a large-scale production
    # server. Enable at your own risk.

    # See the mysql_table(5) manpage for information on the format of this file
    # and the SQL query.

    user = mailconfig
    password = random-password
    hosts =
    dbname = mailconfig
    query = SELECT destination FROM (
              SELECT concat(users.userid,'@',domain_aliases.source) AS source,
                     concat(users.userid,'@',domain_aliases.destination) AS destination
              FROM users,domain_aliases
              WHERE users.domain=domain_aliases.destination
              SELECT * FROM aliases
              # This commented part is experimental. See above for details.
              #SELECT replace(aliases.source,
              #               concat('@', SUBSTRING_INDEX(aliases.source, '@', -1)),
              #               concat('@', domain_aliases.source)
              #              ) AS source,
              #       aliases.destination
              #FROM aliases,domain_aliases
              #WHERE aliases.source regexp concat('@',domain_aliases.destination,'$')
            ) AS alias WHERE alias.source='%s'

    cat << EOF > mysql-virtual-mailbox-domains.cf
    # Generates a list of domain names that postfix should accept mail for, but are
    # not part of $mydomains as these domains are for virtual user mailboxes only.
    # See the virtual(5) manpage for more information.

    # Caveat: The SQL query includes the domains listed in the virtual alias map,
    # but there's no real good way to tell if a domain listed there has any real
    # mailboxes at all without making the queries very expensive. For practical
    # purposes though, it has no real effect if it's listed here or in
    # virtual_alias_domains so you can rest easy.

    # See the mysql_table(5) manpage for information on the format of this file
    # and the SQL query.

    user = mailconfig
    password = random-password
    hosts =
    dbname = mailconfig
    query = SELECT domain FROM (
              SELECT DISTINCT(domain) FROM users
              SELECT DISTINCT(SUBSTRING_INDEX(source, '@', -1)) as domain FROM aliases
            ) virtual_mailbox_domains WHERE domain='%s';

    cat << EOF > mysql-virtual-mailbox-maps.cf
    # Queries the list of virtual users to determine if a given user exists. See
    # the virtual(5) manpage for more information.

    # See the mysql_table(5) manpage for information on the format of this file
    # and the SQL query.

    user = mailconfig
    password = random-password
    hosts =
    dbname = mailconfig
    query = SELECT 1 FROM users WHERE concat(userid, '@', domain)='%s'
    chmod 600 /etc/postfix/mysql-virtual-*.cf

    Postfix now needs to be configured to add support for additional ports (26, 465, 587) as well as to add support for dovecot's local delivery agent (LDA):

    cat << EOF >> /etc/postfix/master.cf
    # Users can use port 26 as an alternative to 25 if it is blocked by their ISP
    26        inet  n       -       n       -       -       smtpd
    # Message submission on port 587 and unofficial use of SMTP+SSL/TLS on port 465
    587       inet  n       -       n       -       -       smtpd
    smtps     inet  n       -       n       -       -       smtpd
      -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes
    # Dovecot LDA, ignores extensions (user+extension@domain.com --> user@domain.com)
    dovecot   unix  -       n       n       -       -       pipe
      flags=DRhu user=mail:mail argv=/usr/libexec/dovecot/deliver -f \${sender} -d \${user}@\${nexthop}

    Finally, add the firewall port exceptions and start postfix:

    chkconfig postfix on
    service postfix start
    iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 25 -j ACCEPT
    iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 26 -j ACCEPT
    iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 465 -j ACCEPT
    iptables -I RH-Firewall-1-INPUT 4 -m state --state NEW -m tcp -p tcp --dport 587 -j ACCEPT
    service iptables save

    Spam and virus filtering: amavisd-new

    First, install amavisd-new and some dependencies:

    yum install amavisd-new spamassassin clamd

    In the /etc/amavisd/amavisd.conf configuration file, the following items need to be changed:

    • Set $max_servers to 15
    • Set $mydomain to 'mail.yourdomain.com'
    • Set $virus_admin, $mailfrom_notify_admin, $mailfrom_notify_recip, and $mailfrom_notify_spamadmin each to "postmaster\@$mydomain"
    • Set $final_banned_destiny to D_DISCARD
    • Set $final_bad_header_destiny to D_DISCARD

    If you wish to send spam to a certain user (ie spam@yourdomain.com) instead of just discarding it, you may also set the $spam_quarantine_to and $bad_header_quarantine_to options.

    Next, Postfix needs to be configured so that it makes use of amavisd's spam/virus filtering:

    cat << EOF >> /etc/postfix/main.cf
    # amavisd
    content_filter = smtp:[]:10024
    default_process_limit = 15
    cat << EOF >> /etc/postfix/master.cf
    # Spam filtering inet n - - - 0 smtpd -o content_filter= -o smtpd_sasl_auth_enable=no

    Unfortunately, the default clamd configuration for amavisd contains some errors, preventing the amavisd.clamd service from starting correctly. We need to overwrite it with this configuration:

    cat << EOF > /etc/clamd.d/amavisd.conf
    # Use system logger.
    LogSyslog yes

    # Specify the type of syslog messages - please refer to 'man syslog'
    # for facility names.
    LogFacility LOG_MAIL

    # This option allows you to save a process identifier of the listening
    # daemon (main thread).
    PidFile /var/run/amavisd/clamd.pid

    # Remove stale socket after unclean shutdown.
    # Default: disabled
    FixStaleSocket yes

    # Run as a selected user (clamd must be started by root).
    User amavis

    # Path to a local socket file the daemon will listen on.
    LocalSocket /var/spool/amavisd/clamd.sock

    Now it is time to enable and start the services.

    chkconfig clamd.amavisd on
    service clamd.amavisd start
    chkconfig amavisd on
    service amavisd start
    service postfix reload

    Response mail autoresponder

    Unfortunately, no CentOS 5 RPM package is currently available for the response daemon. I have attached the SRPM to this page which you can download and rebuild:

    yum install python26 python26-{devel,sqlalchemy,distribute} python-sphinx
    su - regularuser
    wget http://www.firewing1.com/sites/default/files/response-0.8-1.src_.rpm
    rpmbuild --rebuild response-0.8-1.src.rpm
    wget http://www.firewing1.com/sites/default/files/MySQL-python26-1.2.3-1.src_.rpm
    rpmbuild --rebuild MySQL-python26-1.2.3-1.src.rpm

    Note that we are also rebuilding MySQL-python26, a dependency of response. Simply install the resulting two RPMs after the builds finish.

    Before configuring Postfix, we need to create the MySQL tables that will be used to keep information about the autoresponders as well as which users have already been sent an autoresponse within a 24-hour period:

    USE mailconfig;
    CREATE TABLE `autoresponse_record` (
      `id` int(11) NOT NULL auto_increment,
      `sender_id` int(11) NOT NULL,
      `recipient` varchar(255) NOT NULL,
      `hit` datetime NOT NULL default '0000-00-00 00:00:00',
      `sent` datetime NOT NULL default '0000-00-00 00:00:00',
      PRIMARY KEY  (`id`),
      UNIQUE KEY `sender_id` (`sender_id`,`recipient`),
      CONSTRAINT `autoresponse_sender_refs` FOREIGN KEY (`sender_id`) REFERENCES `autoresponse_config` (`id`) ON DELETE CASCADE
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='Response - Autoresponse Notification Records';

    CREATE TABLE `autoresponse_config` (
      `id` int(11) NOT NULL auto_increment,
      `address` varchar(255) NOT NULL,
      `enabled` tinyint(1) NOT NULL,
      `changed` datetime NOT NULL,
      `expires` datetime NOT NULL,
      `subject` varchar(255) NOT NULL,
      `message` longtext NOT NULL,
      PRIMARY KEY  (`id`),
      UNIQUE KEY `address` (`address`)
    ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='Response - Autoresponse Configurations';
    GRANT SELECT ON mailconfig.autoresponse_config TO 'mailresponse'@'localhost' IDENTIFIED BY 'random-password2';
    GRANT SELECT,INSERT,UPDATE ON mailconfig.autoresponse_config TO 'mailresponse'@'localhost' IDENTIFIED BY 'random-password2';
    GRANT SELECT,INSERT,UPDATE ON mailconfig.autoresponse_record TO 'mailresponse'@'localhost' IDENTIFIED BY 'random-password2';

    Similar to before, replace random-password2 with a long and complicated password. You'll never have to remember it, it is simply used internally by the response daemon.

    In the next file, you'll notice we use port 10026 because ports 10024 and 10025 have already been taken by amavisd-new.

    cat << EOF > /etc/postfix/transport_map_response
    response.internal   response:[]:10026
    postmap /etc/postfix/transport_map_response

    Now we need to inform postfix about the transport map and add the bcc mapping that will forward appropriate messages to the response-lmtpd daemon:

    cat << EOF > /etc/postfix/mysql-virtual-autoresponses.cf
    # This file controls the bcc map so that users with an autoresponder enabled  
    # have messages sent through the response transport in addition to their
    # normal transport.
    user = mailresponse
    password = random-password2
    hosts =
    dbname = mailconfig
    query = SELECT '%u#%d@response.internal' FROM autoresponse_config WHERE address = '%s' AND enabled=1
    chmod 600 /etc/postfix/mysql-virtual-autoresponses.cf
    cat << EOF >> /etc/postfix/main.cf
    # Response autoresponder
    response_destination_recipient_limit = 1
    transport_maps = hash:/etc/postfix/transport_map_response
    recipient_bcc_maps = proxy:mysql:/etc/postfix/mysql-virtual-autoresponses.cf
    proxy_read_maps = \$local_recipient_maps \$mydestination \$virtual_alias_maps \$virtual_alias_domains \$virtual_mailbox_maps \$virtual_mailbox_domains \$relay_recipient_maps \$relay_domains \$canonical_maps \$sender_canonical_maps \$recipient_canonical_maps \$relocated_maps \$transport_maps \$mynetworks \$recipient_bcc_maps
    cat << EOF >> /etc/postfix/master.cf
    # Response autoresponder
    response  unix  -       -       n       -       4       lmtp -o disable_dns_lookups=yes
    service postfix reload

    Of course, you'll need to replace random-password2 in /etc/postfix/mysql-virtual-autoresponses.cf with the random password created earlier for the mailresponse user.

    That's it! You can now add a row in the mailconfig.autoresponse_config table to have response-lmtpd automatically reply to any message sent to that user.

    Resources and further reading