Latest version: 1.4.6

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:

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 virtualaliasmaps if you run a multi-domain server).

    It is probably unnecessary to change propagateunmatchedextensions 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