Setting up virtual POP domains and users with qmail
27 november 1997
jbackus@plex.nl


Introduction
------------

This information is not meant to be exhaustive but should give you some ideas
about how to go about setting things up. Feel free to ask for assistance, I
may be able to help. Another place to ask for help would be the djb-password
mailing list at koobera.math.uic.edu, run by ezmlm (as with all ezmlm mailing
lists, send an empty message to djb-password-help@koobera.math.uic.edu for
help on how to {un}subscribe). The software is currently in use at a number of
sites, and I consider it to be fairly stable. It is meant to be used with the
current version of qmail, which is 1.01 as of this time of writing.


Additional software prerequisites
---------------------------------

To run the scripts, you will need:
- a recent version of perl (I use perl5.004 but 5.003 should work);
- the DB_File module (part of the standard perl distribution);
- the Berkeley db-1.8[56] package
  (see, among others, http://mongoose.bostic.com/db/db.185.html).

Additionally, you will need:
- Dan Bernstein's tcpserver, version 0.73.
  This program, which functions as an inetd replacement for TCP-based
  services, is part of his ucspi-tcp package, located at
  http://pobox.com/~djb/ucspi-tcp.html.
- Tim Goodwin's CDB_File module, version 0.7;
- Neil Winton's MD5 module, version 1.7.

See http://www.perl.com and the CPAN modules list for more information about
getting modules (Hint: consider getting the CPAN module for easy module
management!).

Note: Currently chkpoppass uses the TCPLOCALHOST environment variable set by
tcpserver; when run under inetd instead, chkpoppass will use the first entry
in virtualpopdomains as the domain to use (hence, multiple virtual pop domains
are not supported in combination with inetd).


Basic POP information
---------------------

The POP3 protocol - unlike SMTP - doesn't have any way to pass host info from
client to server. Therefore, because there is no way to discern one pop domain
from another, the idea is to run a qmail-pop3d for each pop domain, using
tcpserver.  Each tcpserver is listening on an interface bound to the IP
address associated with the pophost for that domain. That way, chkpoppass can
find out in which pop cdb to look for a user by inspecting the TCPLOCALHOST
environment variable and looking up the corresponding directory and prefix in
the virtualpopdomains file.


Virtual POP accounts without using extra IP addresses
-----------------------------------------------------

Possible workarounds to the above problem include the user specifying a domain
when fetching mail, either by using a prefix (acme-joeuser) or a domain
(joeuser@acme.com). chkpoppass will parse the username, extract the domain
info and consult the appropriate user cdb.  This feature can be turned on by
changing $use_prefix and/or $use_domain near the top of chkpoppass from 0 to
1.

Note that not all mail clients support entering an '@' sign in the local
(user) part of the e-mail address. If this is a problem, you may want to
change the variable $at_sign near the top of chkpoppass to something else
(say, '%'). Don't forget to inform your users of this change!


Installation
------------

These instructions assume that qmail is installed in /var/qmail.

Four scripts and two Makefiles are involved:
- chkpoppass
  Checks a number of authentication sources in a fixed sequence using
  information (username, password, apop_ts) passed to it by qmail-popup, and,
  when the user is authenticated succesfully, starts the program named on its
  commandline (usually qmail-pop3d) in the appropriate directory;
  The sources are:
  1. An APOP secret,
  2. A POP password,
     Both of these are stored in a pop user database, 
     /var/mailhome/dom.ain/poppasswd.cdb,
  3. /etc/passwd,
  4. RPOP authentication; this mechanism depends on the other side having done
     the authentication, and it letting the user use a privileged program
     there, such as nmh's inc(1), which, being setuid root, can bind to a
     reserved port, signaling the user really is who she claims she is.
     Note: This option is not supported by the standard qmail-popup, so it is
     non-funtional by default.
- mkpopdb
  Maintains the pop user databases for each popdomain;
- mkpopuser
  Adds/deletes users from the pop Maildir directory areas belonging to each
  popdomain; takes care of the creation/deletion of subdirectories, Maildirs
  and .qmail files.
- Makefile.pop
  Contains instructions for turning the .db file for each popdomain into a
  .cdb for use by chkpoppass, and updating the users/cdb file;
- Makefile.users
  Contains instructions for building users/cdb.

Major installation steps (commands shown preceded by '#' are examples):

- Extract the distribution:
  # gzcat vqpop.tar.gz | tar xf -
  # cd vqpop
- Check the Makefiles for shell execution syntax.
  You need to enable the correct line(s) for your platform. The syntax used by
  the make command to specify the execution of shell commands differs between
  platforms.
  # vi Makefile.*
- Put the scripts somewhere in your PATH:
  # cp chkpoppass mkpopdb mkpopuser popdom /usr/local/bin
- Create the top-level mailhome directory, /var/mailhome:
  # mkdir /var/mailhome
  Notes:
  - If you want the files/maildirs to live somewhere else, you can change the
    $mailhome/MAILHOME variables in the scripts.
  - This directory and all files/directories below should be owned by a
    separate account, preferably alias or pop. If you want to change this,
    look for the $pop_user/POP_USER variable in the scripts.
- Put the Makefile.pop file in /var/mailhome, and make a symbolic link from it
  to Makefile:
  # cp Makefile.pop /var/mailhome
  # (cd /var/mailhome; ln -s Makefile.pop Makefile)
- Put the Makefile.users file in /var/qmail/users, and make a symbolic link
  from it to Makefile:
  # (cd /var/qmail/users; ln -s Makefile.users Makefile)
- Go to the mailhome directory:
  # cd /var/mailhome
- Create the corresponding popdomain subdirectories:
  # mkdir acme.com example.com
- Create/edit the virtualpopdomains file:
  # vi virtualpopdomains
  You need a virtualpopdomains entry and a corresponding entry in
  /var/qmail/control/virtualdomains for each virtualpopdomain, the first one
  being used for retrieval of mail, the second one for delivery of mail.

Also, a .qmail-vdom-default file needs to be created in ~alias to make the
above virtualdomains-based delivery work. See the next sections for details.


What goes where?
----------------

/var/qmail/
  users/
    Makefile.users
    Makefile (symbolic link to Makefile.users)
  control/
    virtualdomains, contains
      acme.com:alias-vdom-acme     # acme is the prefix in virtualpopdomains;

/var/mailhome/
  Makefile.pop
  Makefile (symbolic link to Makefile.pop)
  virtualpopdomains

/usr/local/bin    # or /var/mailhome/bin, or wherever in PATH
  chkpoppass
  mkpopdb
  mkpopuser
  popdom

~alias/
  .qmail-vdom-default, contains
    |forward "$EXT2"              # ``default'' matches alias-vdom-*


The virtualpopdomains file
--------------------------

This file has the following format:

  domain:hostname:domaindirectory:prefix

Comments and empty lines are currently not allowed.

Descriptions of the various fields:

- domain
    the name of this popdomain, used with the extended POP3 USER syntax: USER
    joeuser@acme.com

- hostname
    the name of the mailhost for this popdomain, used on the tcpserver
    commandline.

    Sample script snippet:

    pop3d_run() {
    THISHOST=$1
    UID=`id -u alias` GID=`id -g alias` QHOME=/var/qmail
    exec tcpserver -R -u $UID -g $GID $THISHOST pop3 \
        $QHOME/bin/qmail-popup $THISHOST \
        /usr/local/bin/chkpoppass $QHOME/bin/qmail-pop3d Maildir &
    }
    pop3d_run mail.acme.com

    Additional notes:
    - tcpserver passes THISHOST on to chkpoppass, using the TCPLOCALHOST
      environment variable.
    - UID should either be
      - the uid of the account that owns the pop directories
        or
      - 0 when Maildirs reside under either /var/mailhome or ~user.  This is
        because qmail-pop3d has to be able to access them so it either needs
        to own them or be run as root.
    - THISHOST needs to be the fully qualified domain name of the mailhost for
      the popdomain.
    - GID should preferably be the gid of the qmail group.

- domaindirectory
    the directory under which the user maildirs reside for this popdmain.

- prefix
    gets prepended to the username in the users/assign file, resulting in name
    resolution through a virtualdomains + ~alias/.qmail-vdom-default.
    If empty, this means a control/locals user.
    Sample users/assign entries:

    +jos-:alias:15139:15102:/var/mailhome/oce.nl/j/jos:-::
    =acme-joeuser:alias:15139:15102:/var/mailhome/acme.com/a/joeuser:::

Here are some sample virtualpopdomains entries:

oce.nl:st1-jos.oce.nl:oce.nl:
acme.com:mail.acme.com:acme.com:acme
isp.net:pophost.isp.net:mboxes.isp.net:isp


Some notes on the files in /var/qmail/users
-------------------------------------------

- The exclude.local file contains names of local accounts who do _not_ wish to
  receive mail locally.
- Mail accounts defined in the append.local file should be listed in
  exclude.local in order to avoid duplicate inclusion by qmail-pw2u.
- Comments/empty lines are not allowed in {append,exclude}.local!


Miscellaneous
-------------

- Address testing using 'chkpoppass -d':

  - example shown with default configuration:

st1-jos:/var/mailhome# TCPLOCALHOST=mail.acme.com chkpoppass -d \
joeuser joepass apop_ts /var/qmail/bin/qmail-pop3d Maildir 
chkpoppass: args: user=joeuser, passwd=joepass, apop_ts=apop_ts,
user_program=/var/qmail/bin/qmail-pop3d
chkpoppass: domaindir=acme.com
chkpoppass: apop: 
chkpoppass: popuser: 1
chkpoppass: shell=/bin/sh, user=joeuser, home=/var/mailhome/acme.com/j/joeuser
+OK 
stat
+OK 1 408
quit
+OK 

  - example shown with '$use_domain = 1':

st1-jos:/var/mailhome# chkpoppass -d joeuser@acme.com joepass apop_ts pwd
chkpoppass: args: user=joeuser@acme.com, passwd=joepass, apop_ts=apop_ts,
user_program=pwd
chkpoppass: domain: user=joeuser, domain=acme.com
chkpoppass: domaindir=acme.com
chkpoppass: apop: 
chkpoppass: popuser: 1
chkpoppass: shell=/bin/sh, user=joeuser, home=/var/mailhome/acme.com/j/joeuser
/var/mailhome/acme.com/j/joeuser

- To make delivery to joeuser@acme.com work, the DNS part would look like:

    acme.com.      IN MX 10 mail.acme.com.
    mail.acme.com. IN A  1.2.3.4

- You can use the -s option of mkpopdb to copy a users' password from an MH
  POP account file to the qmail pop user database. Sample script for bulk
  conversions:

  #!/bin/sh
  # usage: mhpop2qpop domain db|user < POP
  DOM=$1; shift
  CMD=$1; shift
  awk -F: '{print $1, $4}' | while read u p
  do
    case $CMD in
      db)   mkpopdb -a -s $DOM $u $p;;
      user) mkpopuser -a $DOM $u;;
    esac
  done

- Make sure that the poppasswd.{c}db's have mode 660;
  this is because APOP secrets are (necessarily) stored in plaintext form, so
  this file should _not_ be world-accessible.

- Some typical uses:
  - adding a pop user
    # cd /var/mailhome
    # mkpopdb -a acme.com joeuser joepass
      or, in the case of an APOP-authenticated user:
    # mkpopdb -a -s acme.com joeuser joesecret
    # mkpopuser -a acme.com joeuser
    # make
  - deleting a pop user
    # cd /var/mailhome
    # mkpopuser -d acme.com joeuser
    # mkpopdb -d acme.com joeuser
    # make

- The APOP authentication scheme is documented in RFC1939.


Good luck!

Jos Backus
