the djb way

imap


Courier-IMAP


Link: http://www.inter7.com/courierimap/
Version: check for latest stable release
Download: Source Forge
Build type: GNU autoconf
Other: GPL

Sam Varshavchik's Courier-IMAP was the first IMAP server to be built on the Maildir mail-store pioneered with qmail. As a result, Courier has become a very popular IMAP for qmail installations.

The package has been available for a few years now, and is under active development. We recently installed version 2.2.2, dated 20040114.

Although the package is designed for Maildirs, and a lot of the high-level architectural principles are similar to the ideas expressed in djb's work, the overall feel of Courier is somehow very un-djb like. It's a big, ungainly beast, largely reflective of the complexities of the IMAP protocol itself.

Out of the box, Courier is set up to use its own tcpserver-like wrapper, called couriertcpd, but not daemontools supervise. With a little massage, though, Courier can be set up to work very nicely in a system built the djb way.

build/install

The package uses GNU autoconf, so the starting point will be running the familiar ./configure script. It turns out that the key to a good Courier setup for a djb box will be in this initial ./configure-ation.

Once you've downloaded the package, unpack it into some convenient build directory while logged into your non-root account. All the following build steps will be done as a non-root user.

Next, ./configure. Here's the general idea:

$ ./configure \
--prefix=/usr/local/courier-imap \
--without-ipv6 \
--without-authdaemon

The --prefix is to install the package into some familiar, yet out-of-the-way location. The whole thing will be installed under this hierarchy; adjust to suit your own preferences (the default is /usr/lib/courier-imap). We've seen --without-ipv6 before; unless you've patched your other djb utilities for IPv6, you won't need this capability in Courier.

This brings us to the last and most important option, --without-authdaemon. In the default configuration, Courier is designed to operate with a long-running background daemon called authdaemond. The idea is that authdaemond can be set up to serve a number of different IMAP authentication mechanisms, all installed as separate modules. Courier just hands the IMAP client's authentication request over to authdaemond, and authdaemond figures out how to handle the authentication within a separate process.

The design is sound, so why do we disable it?

The result of --without-authdaemon is a simpler system, one that can be thoroughly and consistently run, controlled, monitored, and logged within the framework of the other services we have installed along the djb way.

So, back to running the ./configure script... It's gonna take awhile, as each subdirectory within the archive is ./configureed recursively.

When the configure finally completes, and still as non-root, GNU-make the package, followed by make check :

$ make
$ make check

Now the package is compiled and ready to install. Become super-user, then:

# make install-strip
# make install-configure

All the binaries, man pages, support files, etc., should now be installed on your system, under the path defined with --prefix above.

courier-imapd service

Now that Courier is built, we can get back to all the good djb way stuff again.

As usual, set up your unpriveleged user for the daemon, --"courierd", say-- group "nofiles", homeless, disabled password, nologin shell. Then make the local service directories:

# mkdir -p /var/svc.d/courier-imapd/log

Now install the "run" script for the service in /var/svc.d/courier-imapd/run:


#!/bin/sh
# courier-imap/run
# daemontools service for courier-imap
# ===
CONLIMIT=53
MAILDIR="MAILDIR"

# path to courier installation:
COURIER="/usr/local/courier-imap"

exec 2>&1

# set up envdir from configuration file:
cat ${COURIER}/etc/imapd | ./conf2env.sh ./env

# fire away:
echo "*** Starting courier-imap service..."
echo "*** >>configured for maildir: ${MAILDIR}" 
exec envuidgid courierd envdir ./env softlimit -m5000000\
  tcpserver -v -RH -l0 \
  -c ${CONLIMIT} \
  -x /etc/tcprules/imap.cdb \
  0 143 \
  ${COURIER}/sbin/imaplogin \
    ${COURIER}/libexec/authlib/authpwd \
      ${COURIER}/bin/imapd ${MAILDIR}

### that's all, folks!

Make sure it's executable, chmod 755. The twist here is the use of daemontools' envdir utility, to import the environmental variables defined in Courier's configuration file named ${COURIER}/etc/imapd. The envdir is setup anew each time the service starts, with the help of this shell/sed/awk script named conf2env.sh:


#!/bin/sh
# conf2env.sh
# convert key=value config file to envdir
# ===

# usage:
#
#   conf2env envdir
#
ENVDIR=$1

usage(){
  cat <<END_USAGE
$0 usage:

    cat myfile.conf | $0 envdir

* reads configuration file (key=value format) from stdin
* writes output into envdir specified in argument envdir

END_USAGE
}

if [ "X"${ENVDIR} = "X" ] ; then
  usage
  exit 1
fi

if [ ! -d ${ENVDIR} ] ; then
  echo "error: ${ENVDIR} directory not found"
  exit 1
fi

##
## cleanup input:
sed '
# ignore blank lines, commented lines:
/^$/d
/^ *$/d
/^#/d
# strip leading/trailing blanks:
s/^  *//
s/  *$//
# strip spaces before/after '=':
s/  *\=  */\=/
# strip quotes from quoted values:
s/\="/\=/
s/"$//
# collapse remaining whitespace:
s/[	 ][	 ]*/ /g
# sub tab for key/value field separator:
s/\=/	/
' | \
##
## split key/values and output:
awk -v envdir=${ENVDIR} '
##
## variable "envdir" set from command-line
##
BEGIN {
  FS="\t"
  STDERR="/dev/stderr"
}

{
  if (NF==2) {
    key = $1
    value = $2
    # value may include recursively defined values:
    n = split(value, subval, " ")
    for(i = 1; i <= n; ++i){
      v = subval[i]
      if(v ~ /^\$/){
        sub(/^\$/, "", v)
        if (!sub(/\$[[:alnum:]]+/, values[v], value)){
          print "Unexpected error: RECURSIVE SUBSTITUTION FAILURE:" $0 > STDERR
        }
      }
    }
    values[key] = value
    system("rm -f "envdir"/"key)
    if(system("echo "value" > "envdir"/"key)){
      print "Unexpected error: WRITE ERROR:" $0 > STDERR
    } 
  }
  else {
    print "Unexpected error: EXCESS FIELDS:" $0 > STDERR
  }
}
'
##
## all done:
exit 0

### that's all, folks!

This script should also be owned by root and made executable. To check it out, go ahead and run it as shown, then look at all the files in the ./env subdirectory. Many of these are arguments for the couriertcpd utility, which we don't use. (Someday we might just go through the etc/imapd config file and weed out all those unnecessary variables...)

The friendly multilogger run script:


#!/bin/sh
# courier-imapd/log/run
# multilogger for courier-imapd service
exec setuidgid multilog multilog t /var/multilog/courier-imapd

### that's all, folks!

Install and make executable, chmod 755. Prepare the multilog directory:

# mkdir -p /var/multilog/courier-imapd
# chown multilog /var/multilog/courier-imapd

Now define the tcprules you want for the service in /etc/tcprules/imap.rules:


# imap.rules
127.:allow
192.168.1.:allow
:deny

This permits access only from the local network, 192.168.1.0/24; adjust to suit your requirements, then compile the rules:

# (cd /etc/tcprules; make imap.cdb)

When everything is all set the way you want, link the service into /service to bring it up:

# ln -s /var/svc.d/courier-imapd /service/courier-imapd

Check it out with an ucspi-tcp mconnect dialogue; the stuff we enter is in bold:

$ mconnect "" 143
* OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE
* THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL
* ACL2=UNION] Courier-IMAP ready. Copyright 1998-2004 Double
* Precision, Inc.  See COPYING for distribution information.
a01 login wally beaver
a01 OK LOGIN Ok.
a02 select inbox
* FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
* OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
* 170 EXISTS
* 0 RECENT
* OK [UIDVALIDITY 1074684701] Ok
* OK [MYRIGHTS "acdilrsw"] ACL
a02 OK [READ-WRITE] Ok
a03 logout
* BYE Courier-IMAP server shutting down
a03 OK LOGOUT completed

This example shows plain, unencrypted login, authenticated by the module ${COURIER}/libexec/authlib/authpwd. To enable more authentication options, just add them into the "run" script, after imaplogin:


...
  0 143 \
  ${COURIER}/sbin/imaplogin \
    ${COURIER}/libexec/authlib/authpwd \
    ${COURIER}/libexec/authlib/authcram \
    ${COURIER}/libexec/authlib/authuserdb \
      ${COURIER}/bin/imapd ${MAILDIR}
...

In addition to the modules shown here, Courier can be set up to authenticate with LDAP, PostgreSQL, and MySQL databases. See the documentation for enabling these features.

That's a look at Courier. And running very comfortably now, the djb way.

Note: thanks to Jonas Pasche for sharing his success with running Courier-IMAP under daemontools. See http://jonaspasche.de/courier-imap-daemontools.txt for more information.


Copyright © 2003, 2004 Wayne Marshall.
All rights reserved.

Last edit 2004.01.27, wcm.