Mailman で特定のメールサーバから投稿されたメールは全て配送する

# 概要

Mailman でメーリングリストを運用している時、自組織内から発信されたメールは全て受理・配送したいということがあります。例えば、自組織向けの問い合わせ窓口として Mailman を使っている場合など。本稿では、Mailman の司会判定用モジュールを拡張して、特定のメールサーバから投稿されたメールは全て受理・配送する方法を説明します。

# 背景

Mailman でメーリングリストを運用している時、メーリングリスト非参加者からのメールを受信するには、(1) generic_nonmember_action オプションを「承認」に設定する、または、(2) accept_these_nonmembers オプションに適当な正規表現を設定する、などの選択肢があります。しかし、前者(1)はあらゆるメールを受け付けますし、後者(2)も詐称が容易なヘッダー From に依存していますから、spam に対して脆弱な設定になってしまいます。

本稿では、

1. 自組織の構成員が利用する送信用メールサーバを固定されている。
2. 送信用メールサーバから Mailman に至るまでの経路上のメールサーバは、いずれも信用できる。

という前提のもとで、Received ヘッダの内容に基づいて、送信用メールサーバから投稿されたメールは全て配送するという設定を紹介します。

# 設定例

以下のようなコードを、`/usr/lib/mailman/Mailman/Handlers/TrustedServerModerate.py` として配置します。なお、パス `/usr/lib/mailman/` の部分は、各自の環境にしたがって適宜に読み替えてください。

```py:TrustedServerModerate.py
# Copyright (C) 2015 by TSUCHIYA Masatoshi <tsuchm@gmail.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.

"""This module implements server based moderation.  If a user used one
of trusted remote servers to send his/her message, and only if it has
been delivered through trusted local servers, it is approved as a
trusted message.  Otherwise, it will be handled by the default rule of
Mailman.

USAGE:

Add following codes to mm_cfg.py:

TRUSTEDLOCALSERVER = [ "127.0.0.1",
                       "^192\.168\.10\." ]

TRUSTEDREMOTESERVER = [ "192.168.10.1" ]

GLOBAL_PIPELINE[GLOBAL_PIPELINE.index('Moderate')] = 'TrustedServerModerate'

"""

import re
from Mailman import mm_cfg
from Mailman.Handlers.Moderate import process as moderate_process
from Mailman.Logging.Syslog import syslog

def process(mlist, msg, msgdata):
    def extract_ipaddr(str):
        m = re.search('\[(\d+\.\d+\.\d+\.\d+)\]', str)
        if m:
            return m.group(1)
        m = re.search('\[((?:[a-fA-F0-9]{4}:+)+[a-fA-F0-9]{4})\]', str)
        if m:
            return m.group(1)
        if '[IPv6:::1]'in str:
            return '127.0.0.1'
        else:
            return ''

    def matches_p(ipaddr, patterns):
        for pat in patterns:
            if pat.startswith('^'):
                if re.match(pat, ipaddr):
                    return 1
            else:
                if pat==ipaddr:
                    return 1
        return 0

    def trusted_message_p(message):
        received=[extract_ipaddr(x) for x in message.get_all('Received') if re.match("^from", x)]
        syslog('error', 'Servers which deliver this message are %s', ' / '.join(received))
        received.reverse()
        try:
            while received and matches_p(received[-1], mm_cfg.TRUSTEDLOCALSERVER):
                received.pop()
            remote=0
            while received and matches_p(received[-1], mm_cfg.TRUSTEDREMOTESERVER):
                received.pop()
                remote+=1
            if remote > 0:
                if len(received) <= 1:
                    syslog('error', 'This message is trusted')
                    return 1
                else:
                    return 0
            else:
                return 0
        except AttributeError:
            return 0

    if mlist.generic_nonmember_action == 1 and trusted_message_p(msg):
        return
    return moderate_process(mlist, msg, msgdata)
```

その上で、`/etc/mailman/mm_cfg.py` に以下の3つの指定を記述します。第1に、構成員が利用する送信用メールサーバの IP アドレスを以下のように指定します。

```py
TRUSTEDREMOTESERVER = [ "192.168.10.1",
                        "192.168.10.2" ]
```

なお、本稿の設定では、ここで指定された送信用メールサーバの経由情報に基づいてメール配送の可否を決定します。ですから、送信用メールサーバでは、SMTP AUTH などの方法により認証されたメールのみを受け付ける必要があります。

第2に、送信用メールサーバから、Mailman にたどり着くまでの既知の信頼できるメールサーバの IP アドレスを以下のように指定します。

```py
TRUSTEDLOCALSERVER = [ "127.0.0.1",
                       "192.168.10.3",
                       "192.168.10.4",
                       "192.168.10.5" ]
```

なお、既知の信頼できるメールサーバには、Mailman が稼働しているサーバ自身の IP アドレスを含む必要があります。

文字列の先頭に ^ があれば正規表現として解釈しますので、上記の指定は以下の指定と等価です。

```py
TRUSTEDREMOTESERVER = [ "^192\.168\.10\.[12]$" ]
TRUSTEDLOCALSERVER  = [ "127.0.0.1", "^192\.168\.10\.[345]$" ]
```

第3に、Mailman 本体に付属している `Mailman/Handlers/Moderate.py` の代わりに、上記のモジュールを使うように指定します。

```py
GLOBAL_PIPELINE[GLOBAL_PIPELINE.index('Moderate')] = 'TrustedServerModerate'
```

# 解説

上記の設定例では、以下のような経路で投稿されたメールを、受理・配送します。

```
MUA
 ↓
送信用メールサーバ(192.168.10.1 または 2)
 ↓
中継用メールサーバ(192.168.10.3 または 4)
 ↓
Mailman サーバ(192.168.10.5)
```

それに対して、以下のような経路の場合、送信用メールサーバによるユーザ認証が行われていないので、配送しません。

```
MUA
 ↓
未知メールサーバ
 ↓
送信用メールサーバ(192.168.10.1 または 2)
 ↓
中継用メールサーバ(192.168.10.3 または 4)
 ↓
Mailman サーバ(192.168.10.5)
```

また、以下のような経路の場合も、Received ヘッダの偽造の可能性があるので、配送しません。

```
MUA
 ↓
送信用メールサーバ(192.168.10.1 または 2)
 ↓
未知メールサーバ
 ↓
中継用メールサーバ(192.168.10.3 または 4)
 ↓
Mailman サーバ(192.168.10.5)
```

すなわち、送信用メールサーバ・中継用メールサーバおよび Mailman サーバは自組織の管理下にあって、そのメールサーバが付加する Received ヘッダは信頼できるという前提のもとで経路情報を取り出し、送信用メールサーバでユーザ認証されたメールのみを配送する、という設定になっています。