README.postfix                                                   Jan 28th 2012

The main challenge to setting up Mlmmj with Postfix is that Mlmmj must be
executed by root or the owner of the list directory, but by default Postfix
will execute Mlmmj as 'nobody'[1].

There are a number of possible ways around this:

- Making 'nobody' own your lists (insecure) [2]
- Changing the Postfix default to an 'mlmmj' user (possibly insecure or
  impractical) [3]
- .forward files (impractical) [4]
- Using an :include: file owned by an 'mlmmj' user (possibly insecure and
  suboptimal) [5]
- Adding an alias table owned by an 'mlmmj' user (suboptimal) [6]
- Using a Postfix transport to run Mlmmj as an 'mlmmj' user (recommended)

As you can see, the last option is recommended. Here is how to set it up using
Postfix virtual domains (so you can host multiple domains on the same server).
(It can also be done with regular non-virtual aliases[7].)

 1) Add an 'mlmmj' user to your system (e.g. using 'useradd'). It usually
    makes sense to make this a 'system' user, with no password and no shell
    (/usr/false for the shell), and for its home directory to be
    /var/spool/mlmmj (or wherever you want to put your Mlmmj spool directory).

 2) Create your Mlmmj spool directory (we'll assume it's /var/spool/mlmmj)
    and change its owner to the 'mlmmj' user.

 3) Add an 'mlmmj' transport which uses the pipe(8) delivery agent to execute
    mlmmj-receive as the mlmmj user by adding something like the following to
    master.cf (often in /etc/postfix)[8]:

        # mlmmj mailing lists
        mlmmj   unix  -       n       n       -       -       pipe
            flags=ORhu user=mlmmj argv=/usr/local/bin/mlmmj-receive -F -L /var/spool/mlmmj/$nexthop

    Note that $nexthop is used to specify the list directory. We will return
    to that later.

 4) Integrate some necessary options in main.cf (also often in /etc/postfix):

        # Only deliver one message to Mlmmj at a time
        mlmmj_destination_recipient_limit = 1

        # Consider the part after '+' but before '@' to be an address extension
        # i.e. addresses have the form user+extension@domain.tld
        recipient_delimiter = +

        # A map to forward mail to a dummy domain
        virtual_alias_maps = hash:/var/spool/mlmmj/virtual

        # Allow virtual alias maps to specify only the user part of the address
        # and have the +extension part preserved when forwarding, so that
        # list-name+subscribe, list-name+confsub012345678, etc. will all work
        propagate_unmatched_extensions = virtual

        # A map to forward mail for the dummy domain to the Mlmmj transport
        transport_maps = hash:/var/spool/mlmmj/transport

    Of course, you may need to merge these options with existing ones (e.g.
    you probably have existing virtual_alias_maps if you run a multi-domain
    server).

    It is probably unnecessary to change propagate_unmatched_extensions because
    it defaults to something including 'virtual'. You can check this with
    something like 'postconf | grep propagate'.

 5) (For each list) Create a mailing list (e.g. by using mlmmj-make-ml). The
    list directory should be like /var/spool/mlmmj/list-dir for a flat
    structure, or /var/spool/mlmmj/domain.tld/list-name for a hierarchical
    structure (the -s option to mlmmj-make-ml may be useful to get the list
    created where you want it). Ensure the list directory and everything in it
    is owned by the mlmmj user (except you may want control files to be owned
    by your www server user in order to use web configuration interfaces; they
    must be readable by the mlmmj user though).

 6) (For each list) Add entries to the Postfix tables to accept mail for the
    list and forward it to the Mlmmj transport:

        /var/spool/mlmmj/virtual:
            list-name@domain.tld    domain.tld--list-name@localhost.mlmmj

        /var/spool/mlmmj/transport:
            # for a flat structure
            domain.tld--list-name@localhost.mlmmj   mlmmj:list-dir
            # for a hierarchical structure
            domain.tld--list-name@localhost.mlmmj   mlmmj:domain.tld/list-name

    Note that we have used a dummy domain 'localhost.mlmmj' to connect the
    virtual alias with the Mlmmj transport. This could be anything as long as
    it isn't a real domain. The user part of the address could also be
    anything; as long as the address matches in both tables it should work.

    Also note that the text after 'mlmmj:' becomes $nexthop which was mentioned
    earlier, so it is used to specify the list directory when executing
    mlmmj-receive.

 7) Refresh your postfix tables and reload your configuration so it takes
    effect.

        postmap /var/spool/mlmmj/virtual
        postmap /var/spool/mlmmj/transport
        postfix reload

    Enjoy your new lists!



[1] Actually, the standard local(8) delivery agent will execute external
    programs (such as Mlmmj) as the 'receiving user'. However, unless you
    direct your mail to Mlmmj using a .forward file (see local(8)) or an
    :include: file (see aliases(5)), or your aliases file is not owned by root,
    there is no 'receiving user'. Without a 'receiving user', Postfix uses the
    user from the configuration option 'default_privs', which defaults to
    'nobody'.

[2] Making 'nobody' own your lists is insecure because other programs and
    daemons rely on 'nobody' not owning any files or having access to anything;
    they use 'nobody' as a way of denying access and keeping all your files and
    system secure. Most notably, some NFS implementations use 'nobody' when
    somebody connects but fails to authenticate. Your mailing lists should not
    be accessible in such situations, but they may be if they are owned by
    'nobody'.

[3] Changing 'default_privs' to an 'mlmmj' user may open other security holes,
    and may not be appropriate if Postfix is used for other external programs
    besides Mlmmj.

[4] Using .forward files is not practical, as it requires a user to be created
    for every mailing list.

[5] Using :include: files would require delivery to commands to be enabled in
    :include: files, which is not recommended for security reasons. It is also
    messy for virtual domains in the same way as an alias table owned by an
    'mlmmj' user is[6].

[6] Adding an alias table owned by an 'mlmmj' user works, and doesn't pose any
    great security risk. However, it is messy for virtual domains as you need
    to forward mail from the virtual domain to your non-virtual domain and then
    to Mlmmj. This results in each list having an additional address, which is
    not desirable. That extra intermediate address is also included in mail
    headers, which is not desirable (though it could be filtered out by Mlmmj).
    Setting up an Mlmmj transport is about the same amount of work and doesn't
    have these drawbacks. However, If you are not using virtual domains, this
    is a good and simple option; but it will not be explained in detail here.

[7] To use non-virtual alises, at step 4, you'll need to incorporate:
 
        alias_maps = hash:/var/spool/mlmmj/aliases
        propagate_unmatched_extensions = alias

    You probably will need to adjust propagate_unmatched_extensions in this
    case, probably by adding 'alias' to the existing value rather than using
    'alias' alone.

    If you want to use 'newaliases' to update the alias table, you should also
    incorporate:

        alias_database = hash:/var/spool/mlmmj/aliases

    At step 6, entries in /var/spool/mlmmj/aliases should look something like:

        list-name:    list-name@localhost.mlmmj

    At step 7, you'll need:

        postalias /var/spool/mlmmj/aliases

    or (if you included alias_database above)

        newaliases

    And of course you can omit the virtual stuff if you're not using it.

    Note that this has not been tested, but we believe it should work.

[8] The flags for the transport are pretty critical. In particular if the 'R'
    option is not used mlmmj-receive fails to receive the mail correctly. The
    options mean:

        D - Prepend a 'Delivered-To: recipient' header (not used)
        O - Prepend an 'X-Original-To: recipient' header
        R - Prepend a 'Return-Path:'. header
        h - fold $nexthop to lowercase
        u - fold $recipient to lowercase



