2022/12/25(日)メールサーバの中規模改修と基礎知識(3)~ Dovecot+Pigeonhole,cert-bot

次は、電子メール受信の中核を成す dovecot まわりの構築です。
dovecot は、どうやら「ダブコット」或いは「ダヴコット」と称するようです。
日本人だと、どうしても「どべこっと」と言いたくなるのではないかと思う。。

概要

dovecot は、2022/12/22 に最新版 Ver 2.3.20 がリリースされました。
今回、dovecot で実現する構成は、概ね下記のような、下手くそな手書きで描いたようなものになります。
20221225_dovecot_2022.png

【注:この構成図は電子メール受信処理に特化したイメージ図であり、実際の dovecot 内部構成を示したものではありません。
   むしろ全く無関係です。】


構築するメールサーバの Port 110,143,993,995 で電子メールを受信するアクションを待ち、そのアクションに従ってサーバ上で受信している電子メールメッセージをダウンロードするのが、dovecot の主な任務です。

POP3 も IMAP4 も受信者を認識する個別情報(アカウント名とパスワード) にてユーザ認証をし、受信者固有のメールボックスを特定する処理を行います。そのような情報は、弊社では OpenLDAP のデータベースにて管理しており、そのために、OpenLDAP へアカウント問い合わせを行う Port 389 が使われます。OpenLDAP のSSL/TLS 対応ポートに Port 636 がありますが、OpenLDAP を同じサーバ上で稼働させるため、localhost:389 への接続となり、SSL/TLS は不要です。

また、Postfix から、配送されてくる電子メールメッセージが、LMTP インタフェースを介して流れてきます。
LMTP インタフェースは、unix ソケットで実現します。こうすることで、サーバのリソース消費を大きく増やさずに且つ高速に受信処理が出来るようになります。LMTP でも受信者のメールボックスを特定させるために、OpenLDAP へのアカウント問い合わせを実施します。

dovecotのインストール

ここからは、基本的に root アカウントでの作業となります。
「ソースコードのコンパイルは、root以外の一般ユーザで行うべき」というポリシーを堅持する開発者が居られますが、大抵の場合、上手くいきません。

dovecot をインストールする場合、FreeBSD13 では、下記のモジュールが事前に必要です。(但し、2022/12/24 現在)
これらの多くは、Ports や Package でインストールしても、管理上特段問題にはなりません。
 ・perl5-5.36.0	(Ports から カテゴリ:lang)
 ・autoconf-2.71	(Ports から カテゴリ:devel)
 ・libltdl-2.4.7	(Ports から カテゴリ:devel)
 ・libtool-2.4.7	(Ports から カテゴリ:devel)
 ・pcre2-10.40		(Ports から カテゴリ:devel)
 ・OpenLDAP 2.6.3 以上 (これはソースコードからの構築を強く推奨)
このあたりは、バージョンアップで刻々変わっていくこともあるのですが、管理する以上は、そのような変化を知ることが重要です。
Ports や Package では、このあたりを余計なモジュールまで自動インストールしてしまうことが多々あり、「いざトラブル!」という際に却って対応困難になる原因を作ります。
それが嫌なので、要所なサーバサイドソフトウェアについては、Ports や Package に頼らずに、敢えてソースコードからの構築を行い、技術対応力低下の防止に努力しています。

更に、インストールに先立ち、dovecot のセキュリティポリシーに従うため、ユーザ dovecot・ユーザ dovenull を、vipw や useradd コマンドで追加します:
dovecot::2002:3000::0:0:dovecot MDA:nonexistent:/usr/sbin/nologin
dovenull::2007:3000::0:0:dovecot MRA:nonexistent:/usr/sbin/nologin
vipw でユーザ追加した場合、/etc/group に下記の1行を追加しておきます(あとでこれが重要になってくる):
mailuser:*:3000:
更に vipw でユーザ追加した場合は、つまらないセキュリティホールを作らないために、下記コマンドも一応実行しておきます:
# passwd dovecot
# passwd dovenull
はい。いよいよコンパイル作業です。下記のように順に実施していきます:
# cp dovecot-2.3.19.1.tar.gz /usr/local/src
# cd /usr/local/src
# tar xvzf dovecot-2.3.19.1.tar.gz
# cd dovecot-2.3.19.1
 	 
# setenv CPPFLAGS '-I/usr/local/include -I/usr/include'	(configure が上手くライブラリを探せないため)
# setenv LDFLAGS '-L/usr/local/lib -L/usr/lib'     (configure が上手くライブラリを探せないため)
# setenv LD_LIBRARY_PATH '/usr/local/lib /usr/lib'   (コンパイルが上手くいかないため)

※以下、説明のために改行していますが、 ./configure の部分は、改行せずに、半角スペース区切りで、一気に入力。
# ./configure --sysconfdir=/usr/local/etc (設定ファイル群を格納するディレクトリ)
       --localstatedir=/var     (unix ソケットなどを格納するルートディレクトリ)
       --with-ldap=yes       (LDAP 認証をサポートする)
       --with-ssldir=/usr      (openssl のインストール位置を知らせる)
       --with-zlib         (zlib[gz 形式圧縮] をサポートする)
       --with-libwrap        (TCP Wrapper をサポートする)
       --without-bsdauth      (BSD 認証はサポートしない)
       --without-pam         (pam 認証はサポートしない)
       --without-nss        (nss はサポートしない)
上記は、dovecot 2.3.19.1 の場合で、バージョンが変わると、この部分も変わります。適宜解釈を。
./configure が成功すると、下記のような情報が出力されます:
Install prefix . : /usr/local
File offsets ... : 64bit
I/O polling .... : kqueue
I/O notifys .... : kqueue
SSL ............ : yes (OpenSSL)
GSSAPI ......... : no
passdbs ........ : static passwd passwd-file checkpassword ldap
CFLAGS ......... : -std=gnu99 -g -O2 -fstack-protector-strong -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -Wall -W -Wmissing-prototypes -Wmissing-declarations -Wpointer-arith -Wchar-subscripts -Wformat=2 -Wbad-function-cast -Wno-duplicate-decl-specifier -Wstrict-aliasing=2
         : -shadow -pam -bsdauth -sia -vpopmail -sql
userdbs ........ : static prefetch passwd passwd-file checkpassword ldap
         : -vpopmail -sql
SQL drivers .... :
         : -pgsql -mysql -sqlite -cassandra
Full text search : squat
         : -lucene -solr
ここでは、OpenLDAP でユーザ管理をすることを前提にしているため、passdbs 行と userdbs 行に 'ldap' の文字列が存在することを確認します。
# make
# make install
# cd /usr/local/libexec/dovecot
# chmod 4750 dovecot-lda	(setuid でメール配信出来るようにする)
# chgrp mailuser dovecot-lda	(setuid でメール配信出来るようにする)
結構、大きなプログラム群なので、make は、そこそこの時間がかかります。
dovecot-lda は、Courier maildrop に相当する実行モジュールで、setuid を可能にすることで、後述する pignonhole で、メールアカウント毎の動作が可能になります。

次に、
# cd /usr/local/etc/dovecot
として、設定内容をこのディレクトリに記述していきます。dovecot 2.x では、設定ファイルが細かく分かれており、既存Webサイトにおける同種の説明も、教科書的にそれに沿ったものになっていますが、却って管理しにくくなるという我儘から、最低限の設定ファイルを上記ディレクトリに書き込んでいきます。

まずは、 dovecot.conf から〔注:不要な設定があるかもしれません(当方の環境では警告もエラーも出ません)〕:
auth_cache_negative_ttl = 30 mins
auth_cache_size = 10 M
auth_cache_ttl = 30 mins
auth_mechanisms = cram-md5 digest-md5 plain login scram-sha-1 apop
auth_username_chars = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_@
disable_plaintext_auth = no
first_valid_uid = 3000
last_valid_gid = 4000
last_valid_uid = 4999
mail_location = maildir:/var/mail/%h

!include ssl_sni.conf

passdb {
  args = /usr/local/etc/dovecot/dovecot-ldap.conf
  driver = ldap
}
protocols = imap pop3 lmtp

plugin {
  sieve = ~/.dovecot.sieve
  sieve_plugins = sieve_extprograms
  sieve_extensions = +vnd.dovecot.filter
  sieve_filter_bin_dir = /usr/local/etc/dovecot/sieve-filter
}
  unix_listener /var/spool/postfix/private/auth {
    group = postdrop
    mode = 0660
    user = postfix
  }
  user = root
  vsz_limit = 128 M
}
service imap-login {
  chroot =
  executable = /usr/local/libexec/dovecot/imap-login
  inet_listener imap {
    address = *,[::]
    port = 143
  }
  inet_listener imaps {
    address = *,[::]
    port = 993
  }
  user = dovecot
  vsz_limit = 32M
}
service imap {
  executable = /usr/local/libexec/dovecot/imap
}
service imaps {
  executable = /usr/local/libexec/dovecot/imap
}

service pop3-login {
  chroot =
  executable = /usr/local/libexec/dovecot/pop3-login
  inet_listener pop3 {
    address = *,[::]
    port = 110
  }
  inet_listener pop3s {
    address = *,[::]
    port = 995
  }
  user = dovecot
}
service pop3 {
  executable = /usr/local/libexec/dovecot/pop3
}
service pop3s {
  executable = /usr/local/libexec/dovecot/pop3
}

service lmtp {
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0666
    user = postfix
    group = mailuser
  }
}

service stats {
  unix_listener stats-reader {
    user = vmail
    group = mailuser
    mode = 0660
  }
  unix_listener stats-writer {
    user = vmail
    group = mailuser
    mode = 0660
  }
}

userdb {
  args = /usr/local/etc/dovecot/dovecot-ldap.conf
  driver = ldap
}

protocol imap {
  imap_logout_format = bytes=%i/%o
  mail_plugins = $mail_plugins imap_filter_sieve
}
protocol imaps {
  imap_logout_format = bytes=%i/%o
  mail_plugins = $mail_plugins imap_filter_sieve
}

protocol pop3 {
  pop3_uidl_format = %08Xu%08Xv
}
protocol pop3s {
  pop3_uidl_format = %08Xu%08Xv
}

protocol lmtp {
  postmaster_address = postmaster@mx.example.com
  mail_plugins = $mail_plugins sieve
}
protocol lda {
  auth_socket_path = /var/run/dovecot/auth-master
  hostname = mx.example.com
  mail_plugins = sieve
  postmaster_address = postmaster@mx.example.com
  sendmail_path = /usr/sbin/sendmail
}
次に、 dovecot-ldap.conf〔注:構築する OpenLDAP におけるスキーマ設定に合わせた設定に適宜変えてください。〕
hosts = localhost
sasl_bind = no
tls = no
ldap_version = 3
base = dc=%d,dc=control,dc=isp
deref = never
scope = subtree

user_attrs = homeDirectory=home=/var/mail/%$,uidNumber=uid,gidNumber=gid,mail=mail
user_filter = (&(cn=%u)(status=valid))

pass_attrs = uid=cn,userPassword=password,\
homeDirectory=userdb_home,uidNumber=userdb_uid,gidNumber=userdb_gid
pass_filter = (&(cn=%u)(status=valid))

default_pass_scheme = PLAIN
最後に、ssl_sni.conf〔注:実ホスト名と、収容する仮想ドメインのホスト名を設定・記述します。〕
最初の2行は、実ホスト名に対応するサーバ証明書の設定、local_name のブロックは、実ホスト名と収容する仮想ドメインのホスト名を記述します。
ここで、「収容する仮想ドメインのホスト名」は、メールサーバ利用者が設定する「受信メールサーバ名」にそのまま対応します。
ssl_cert = </usr/local/etc/letsencrypt/live/mx.example.com/fullchain.pem
ssl_key = </usr/local/etc/letsencrypt/live/mx.example.com/privkey.pem

local_name mx.example.com {
  ssl_cert = </usr/local/etc/letsencrypt/live/mx.example.com/fullchain.pem
  ssl_key = </usr/local/etc/letsencrypt/live/mx.example.com/privkey.pem
}

local_name mx.example2.net {
  ssl_cert = </usr/local/etc/letsencrypt/live/mx.example2.net/fullchain.pem
  ssl_key = </usr/local/etc/letsencrypt/live/mx.example2.net/privkey.pem
}
以上で、dovecot の設定は終わりですが、環境が整っていないため、まだ稼動させることが出来ません。
なので、次の項目へ進みます。

Pigeonhole のインストール

2022/12/22 に、最新版 0.5.20 がリリースされました。
Pigeonhole(ピゲオンホール?)は、本来は、受信メールの振り分けを行うものですが、ここでは spam判定・コンピュータウィルス判定されたメールの隔離を行う機能を実現するために使用します。
Pigeonhole では、振り分け条件を記述するスクリプト言語に「sieve(シーヴ)」というものを採用しています。投稿日現在、RFC 9042 で規定されており、技術規格的な標準化が試みられています。

標準仕様の sieve には、Courier maildrop にあるような外部プログラムを呼び出して、メッセージフィルタのような機能がありません。しかしながら、Pigeonhole には、Ver 0.2 で、拡張機能という形で該当機能が実装され、Ver 0.3で 「Extprograms Plugin」という名称に変わって機能強化され、Ver 0.4 以降は標準機能として提供されていることを知り、これで、maildrop に代わる手段が sieve スクリプトで実現できるようになります。

Pigeonhole をインストールする場合、FreeBSD13 では、下記のモジュールが事前に必要です。(但し、2022/12/24 現在)
これらの多くは、Ports や Package でインストールしても、管理上特段問題にはなりません。
また、先述の dovecot における必要モジュールと重複しているものが多いですが、確認の意味で敢えて掲載しており、再インストールの必要はありません。
 ・perl5-5.36.0 	 (Ports から カテゴリ:lang)
 ・autoconf-2.71	 (Ports から カテゴリ:devel)
 ・libltdl-2.4.7	 (Ports から カテゴリ:devel)
 ・libtool-2.4.7	 (Ports から カテゴリ:devel)
 ・pcre-8.45		 (Ports から カテゴリ:devel)
 ・pcre2-10.40		 (Ports から カテゴリ:devel)
 ・OpenLDAP 2.6.3 以上	 (これはソースコードからの構築を強く推奨)
 ・dovecot 2.3.19.1 以上(前項でソースコードからの構築)
Pigeonhole は、dovecot 本体への付加機能的な形でインストールされていきます。
下記の手順で、コンパイル・構築していきます:
# cp dovecot-2.3-pigeonhole-0.5.19.tar.gz /usr/local/src	 
# cd /usr/local/src	 
# tar xvzf dovecot-2.3-pigeonhole-0.5.19.tar.gz	 
# cd dovecot-2.3-pigeonhole-0.5.19	 
 	 
# setenv CPPFLAGS '-I/usr/local/include -I/usr/include'	(configure が上手くライブラリを探せないため)
# setenv LDFLAGS '-L/usr/local/lib -L/usr/lib'		(configure が上手くライブラリを探せないため)
# setenv LD_LIBRARY_PATH '/usr/local/lib /usr/lib' 	(コンパイルが上手くいかないため)
# ./configure --with-dovecot=/usr/local/lib/dovecot 	(dovecot の構築環境を参照して構築)
# make
# make install
上記は、pigeonhole 0.5.19 の場合で、バージョンが変わると、この部分も変わります。適宜解釈を。

このモジュールは、これでインストール完了です。

certbot を使って、Let's Encrypt のサーバ証明書を活用する

無償利用が可能なサーバ証明書を取得・管理するのに不可欠です。 現行のdovecot と postfix は SSL/TLS のSNI に対応しているのと、いわゆる「オレオレ証明書」を、昨今のアンチウィルスソフトは受け付けなくなってきているので「やむを得ず」といったところです。有効期間が90日と短いのがネックですが、自動更新を確実に行わせることで、このデメリットの補完を試みます。

certbot は、Python で記述されているため、FreeBSD13 では、下記のモジュールが事前に必要です。(但し、2022/12/24 現在)
Ports や Package でインストールしても、管理上特段問題にはなりません。
既にインストールされている場合、再インストールの必要はありません。
 ・python39-3.9.16 	 (Ports から カテゴリ:lang)
また、このモジュールは、FreeBSD においては、Ports からのインストールでも管理上の問題はありません。
以下の手順で、不足している依存モジュールを自動検出しつつ、インストールを実行します:
# cd /usr/ports/security/py-certbot
# make install
# make clean
# cd
# rehash
インストールしたら、早速、必要なサーバ証明書を取得します。
稼動サーバ上で Apache や nginx などの Webサーバが稼動している場合は、一旦停止します。

また、外部からのアクセスを制限している場合は、それらを一旦解除します。(Let's Encrypt の認証局サーバが非公開のため)
参考までに、当方の開発環境では、Apache を動作させ、加えてサーバ攻撃のためにアクセス制限を、サーバ本体で水際対策で行っているため、それを含めて以下の手順となります:
# /usr/local/etc/rc.d/apache2 stop
# /sbin/pfctl -d
# /usr/local/bin/certbot certonly --standalone --agree-tos -m user@example.com -d mx.example.jp
# /usr/local/bin/certbot certonly --standalone --agree-tos -m user@example.com -d mx.example2.net
−m の後ろに指定するメールアドレスは、有効期限が近づいてきたり、何かあった際の連絡先として登録するメールアドレスを指定します。了承なく他人のメールアドレスにしたり、存在しないメールアドレスを指定したりしないようにしましょう。

サーバ証明書の取得に成功すると、/usr/local/etc/letsencrypt/live 配下にサーバ証明書等が作成されているはずです。

取得が成功したら、環境を元に戻します。
# /sbin/pfctl -e
# /usr/local/etc/rc.d/apache2 start
肝心の自動更新ですが、週に一度実行する形で cron 設定するとよいでしょう。
まずは、定期的に実行する sh スクリプトを下記のように /usr/local/etc/letsencrypt_renew.sh に作成しました:
#!/bin/sh

/usr/local/etc/rc.d/apache2 stop
/sbin/pfctl -d
/usr/local/bin/certbot renew -m user@example.com
/sbin/pfctl -e
/usr/local/sbin/postfix reload
/usr/local/sbin/dovecot reload
/usr/local/etc/rc.d/apache2 start
この sh スプリプトは、cron にて root で実行させ、実行権限を与えるのを忘れないようにしてください。
/usr/local/bin/certbot renew で、有効期限が30日未満の証明書のみを更新します。

この際、証明書を生成した環境で更新をしようとするので、証明書を生成した時と同じ環境に仕立てる必要があります。