私的サーバー構築日誌：SSSDとDovecot

# 能書き

[私的サーバー構築日誌：仕切り直し](https://qiita.com/katz_engineer/items/1f21de2bd9072c260ae6)からの自宅サーバー構築の続きです。

今、メールサーバー(MTA)を立てるのは大変なんですね。迷惑メール対策でガッチガチに固められて、気を使わなきゃいけない点が増えてます。技術的にかなり面倒な感じ。そしてGoogleとかの世界的大手メールサービスがそれを強要してます。やって出来ない事はないけれど、とても大変です。

そんな面倒臭いだけのレガシーなメールサーバーを今立てようとする理由は、GitLabです。この後でGitLabサーバーを立てたいと考えていますが、そこでメールサーバーとの連携が必要になります。これが今のモチベーションになっています。

[以前](https://qiita.com/katz_engineer/items/9e8716c8bdf2bb1a54a8)はDovecotを3回に分けて設定しましたが、今回は一気に全部やります。

なお、今回もLXDコンテナに設定します。その意味ですが。

- 受信メールをコンテナ内に蓄積できます。Dockerコンテナではコンテナ内へのデータ蓄積は不向きです。ホスト側のディレクトリに保存させる手段もありますけども、LXDコンテナならそのままコンテナ内に蓄積できます。
- 蓄積したメールのバックアップコピーを作ろうとした場合、LXDコンテナ内にメールがあれば、コンテナをエクスポートする事でDovecotの設定ごとバックアップできます。
- 将来はDRBDも入れて、リアルタイムでミラーリングして「絶対に喪失しないメールシステム」を実現したい。こういった複数ソフトのインストールにはDockerは不向きでしょう。

そういう訳で、常時起動のLXDコンテナを立てて、その中にMDAとしてDovecotをインストールします。MTAは、志を低くした形で次回立てます。

# 目標

- LXDコンテナにSSSDを設定してLDAPと連携
- そのコンテナにDovecotをインストールしてIMAPSとLMTPを設定
- cloud-initを利用して自動化可能な部分は自動化する

POP3は使いません。どうせ私専用のメールサーバーですので、私自身が使わない以上、選択肢として用意しておく必要もありません。

そして IMAP over SSL/TLS、通称 IMAPS にします。ただのIMAPは使えないように潰しておきます。

# 参考文献

Dovecot関連。

- [自宅サーバー構築譚：LXDコンテナにDovecotをインストール / LMTP - Qiita](https://qiita.com/katz_engineer/items/9e8716c8bdf2bb1a54a8)
- [自宅サーバー構築譚：LXDコンテナにDovecotをインストール / IMAP - Qiita](https://qiita.com/katz_engineer/items/2fdb627714c6967019f5)

SSSDについて。

- [自宅サーバー構築譚：LXDコンテナにDovecotをインストール / SSSDでLDAPと連携 - Qiita](https://qiita.com/katz_engineer/items/88ba6652b9ee9d6c7df1)
- [私的サーバー構築日誌：LDAPサーバー OpenLDAP・SSSD - Qiita](https://qiita.com/katz_engineer/items/11c1723e38c760227d35)

証明書関係。

- [認証局(CA)の作成 - 私的サーバー構築日誌：オレオレ認証局 - Qiita](https://qiita.com/katz_engineer/items/ff07466662131b72c14c)
- [私的サーバー構築日誌：オレオレ認証局が認証する証明書 - Qiita](https://qiita.com/katz_engineer/items/fe954644fa570093ca3a)

LXDコンテナの初期化処理を自動化するcloud-initについて。

- [自宅サーバー構築譚：LXDでcloud-initによる初期設定の自動化 - Qiita](https://qiita.com/katz_engineer/items/7ac5cadce3bdde9c76dc)

# LXDコンテナの用意

Dovecot（及びメール蓄積）の為のLXDコンテナを作成します。

コンテナ名とそのIPアドレス、ポート番号などは何度か参照するので、環境変数に設定しておきます。異なる設定を使いたい場合は、ここを修正して下さい。

```console:ホスト側
CONTAINER=dovecot1
IPADDRESS=192.168.0.201
IMAPS_PORT=993
LMTP_PORT=24
HOST_IPADDR=172.16.1.2
```

LXDコンテナを初期設定します。今回は```/etc```のSubversion管理は設定しません。煩雑なだけで役に立ちそうにないので。

```console:ホスト側
lxc init images:ubuntu/jammy/cloud $CONTAINER --device eth0,ipv4.address=$IPADDRESS
lxc config set $CONTAINER cloud-init.user-data="$(cat <<___
#cloud-config
timezone: Asia/Tokyo
locale: ja_JP.utf8
package_upgrade: true
packages:
  - sssd-ldap
  - sssd-tools
  - dovecot-lmtpd
  - dovecot-imapd
___
)"
lxc start $CONTAINER
lxc exec $CONTAINER -- cloud-init status --wait
```

# 証明書の用意

[以前用意したオレオレ認証局](https://qiita.com/katz_engineer/items/ff07466662131b72c14c#%E8%AA%8D%E8%A8%BC%E5%B1%80ca%E3%81%AE%E4%BD%9C%E6%88%90)と[証明書](https://qiita.com/katz_engineer/items/fe954644fa570093ca3a#%E9%8D%B5%E7%94%9F%E6%88%90)のパスは下記の通りです。

認証局：

- 秘密鍵 ```/home/cert/demoCA/pki/private/ca.key```
- 公開鍵 ```/home/cert/demoCA/pki/ca.crt```

サーバーマシンprimary.homeの証明書：

- 秘密鍵 ```/home/cert/demoCA/pki/private/primary.home.key```
- 公開鍵 ```/home/cert/demoCA/pki/issued/primary.home.crt```

# Dovecot

## 認証局と証明書

Dovecot-IMAPで必要な証明書は、書き方が他とはちょっと違うようです。証明書と認証局の鍵を結合したpemファイルを作成します。

```console:ホスト側
mkdir /tmp/dovecotIMAP
cd /tmp/dovecotIMAP
```

```console:ホスト側
sudo -u cert cat /home/cert/demoCA/pki/private/primary.home.key >dovecot_primary.pem
```

```console:ホスト側
sudo -u cert openssl x509 -outform pem -in /home/cert/demoCA/pki/issued/primary.home.crt >>dovecot_primary.pem
```

```console:ホスト側
sudo -u cert cat /home/cert/demoCA/pki/ca.crt >>dovecot_primary.pem
```

このpemファイルをLXDコンテナ内の所定の場所に置きます。

```console:ホスト側
lxc file push /tmp/dovecotIMAP/dovecot_primary.pem $CONTAINER/etc/dovecot/private/dovecot_primary.pem
```

終わったら、ホスト側のファイルは不要なので削除します。

```console:ホスト側
cd ..
rm -r dovecotIMAP
```

## Dovecotの設定

LMTPとIMAPSを一緒に設定します。

まずは作業場所を確保。

```console:ホスト側
mkdir /tmp/dovecotPatch
cd /tmp/dovecotPatch
```

次にパッチを用意します。

```console:ホスト側
cat >dovecot.patch <<___
Index: /etc/dovecot/conf.d/10-auth.conf
===================================================================
--- /etc/dovecot/conf.d/10-auth.conf
+++ /etc/dovecot/conf.d/10-auth.conf
@@ -48,7 +48,7 @@
 # the standard variables here, eg. %Lu would lowercase the username, %n would
 # drop away the domain if it was given, or "%n-AT-%d" would change the '@' into
 # "-AT-". This translation is done after auth_username_translation changes.
-#auth_username_format = %Lu
+auth_username_format = %Ln

 # If you want to allow master users to log in by specifying the master
 # username within the normal username string (ie. not using SASL mechanism's
Index: /etc/dovecot/conf.d/10-logging.conf
===================================================================
--- /etc/dovecot/conf.d/10-logging.conf
+++ /etc/dovecot/conf.d/10-logging.conf
@@ -4,7 +4,7 @@

 # Log file to use for error messages. "syslog" logs to syslog,
 # /dev/stderr logs to stderr.
-#log_path = syslog
+log_path = syslog

 # Log file to use for informational messages. Defaults to log_path.
 #info_log_path =
@@ -14,7 +14,7 @@
 # Syslog facility to use if you're logging to syslog. Usually if you don't
 # want to use "mail", you'll use local0..local7. Also other standard
 # facilities are supported.
-#syslog_facility = mail
+syslog_facility = mail

 ##
 ## Logging verbosity and debugging.
Index: /etc/dovecot/conf.d/10-mail.conf
===================================================================
--- /etc/dovecot/conf.d/10-mail.conf
+++ /etc/dovecot/conf.d/10-mail.conf
@@ -27,7 +27,7 @@
 #
 # <doc/wiki/MailLocation.txt>
 #
-mail_location = mbox:~/mail:INBOX=/var/mail/%u
+mail_location = maildir:~/Maildir

 # If you need to set multiple mailbox locations or want to change default
 # namespace settings, you can do it by defining namespace sections.
Index: /etc/dovecot/conf.d/10-master.conf
===================================================================
--- /etc/dovecot/conf.d/10-master.conf  (revision 3)
+++ /etc/dovecot/conf.d/10-master.conf  (working copy)
@@ -52,16 +52,16 @@
 }

 service lmtp {
-  unix_listener lmtp {
+  #unix_listener lmtp {
     #mode = 0666
-  }
+  #}

   # Create inet listener only if you can't use the above UNIX socket
-  #inet_listener lmtp {
+  inet_listener lmtp {
     # Avoid making LMTP visible for the entire internet
     #address =
-    #port =
-  #}
+    port = $LMTP_PORT
+  }
 }

 service imap {
Index: /etc/dovecot/conf.d/10-ssl.conf
===================================================================
--- /etc/dovecot/conf.d/10-ssl.conf
+++ /etc/dovecot/conf.d/10-ssl.conf
@@ -9,8 +9,8 @@
 # dropping root privileges, so keep the key file unreadable by anyone but
 # root. Included doc/mkcert.sh can be used to easily generate self-signed
 # certificate, just make sure to update the domains in dovecot-openssl.cnf
-ssl_cert = </etc/dovecot/private/dovecot.pem
-ssl_key = </etc/dovecot/private/dovecot.key
+ssl_cert = </etc/dovecot/private/dovecot_primary.pem
+ssl_key = </etc/dovecot/private/dovecot_primary.pem

 # If key file is password protected, give the password here. Alternatively
 # give it when starting dovecot with -p parameter. Since this file is often
___
```

必要な設定ファイルを取り出します。

```console:ホスト側
mkdir etc
lxc file pull $CONTAINER/etc/dovecot/conf.d/{10-auth.conf,10-logging.conf,10-mail.conf,10-master.conf,10-ssl.conf} etc
```

そうしたら```patch```コマンドで一気に適用！
タブ文字の関係などでエラーが発生する可能性があるので、```--ignore-whitespace```オプションを付けて実行します。

```console:ホスト側
patch --ignore-whitespace -d etc <dovecot.patch
```

エラーが表示されなければ成功です。

```console:ホスト側
$ patch --ignore-whitespace -d etc <dovecot.patch
patching file 10-auth.conf
patching file 10-logging.conf
patching file 10-mail.conf
patching file 10-master.conf
patching file 10-ssl.conf
```

パッチを当てた設定ファイルをコンテナに戻します。

```console:ホスト側
lxc file push etc/* $CONTAINER/etc/dovecot/conf.d/
```

Dovecotに設定を読み込ませます。

```console:ホスト側
lxc exec $CONTAINER systemctl reload dovecot
```

不要になったファイル達は削除しましょう。

```console:ホスト側
cd ..
rm -r dovecotPatch
```

## LXDコンテナのネットワークの設定

現在のネットワークの状態を確認してみましょう。

```console:ホスト側
lxc network list-leases lxdbr0
```

今の状態ならこんな風に表示されるでしょう。コンテナを既に色々弄っていたら、違う表示になるかも知れません。

```console:ホスト側
$ lxc network list-leases lxdbr0
+-----------+-------------------+---------------+---------+
| HOSTNAME  |    MAC ADDRESS    |  IP ADDRESS   |  TYPE   |
+-----------+-------------------+---------------+---------+
| dovecot1  | 00:16:3e:fb:bf:ed | 192.168.0.201 | STATIC  |
+-----------+-------------------+---------------+---------+
| lxdbr0.gw |                   | 192.168.0.1   | GATEWAY |
+-----------+-------------------+---------------+---------+
```

コンテナのNATを設定します。

```console:ホスト側
lxc network forward create lxdbr0 $HOST_IPADDR
lxc network forward port add lxdbr0 $HOST_IPADDR tcp $LMTP_PORT $IPADDRESS $LMTP_PORT
lxc network forward port add lxdbr0 $HOST_IPADDR tcp $IMAPS_PORT $IPADDRESS $IMAPS_PORT
```

# SSSD

## 認証局

まずは認証局の公開鍵を用意します。

```console:ホスト側
sudo -u cert cp -p /home/cert/demoCA/pki/ca.crt /tmp/demoCA.crt
```

```console:ホスト側
sudo chown $USER: /tmp/demoCA.crt
```

```console:ホスト側
chmod 644 /tmp/demoCA.crt
lxc exec $CONTAINER mkdir /usr/share/ca-certificates/demoCA
```

```console:ホスト側
lxc file push /tmp/demoCA.crt $CONTAINER/usr/share/ca-certificates/demoCA/demoCA.crt
rm /tmp/demoCA.crt
```

次に認証局を信頼します。

```console:ホスト側
lxc shell $CONTAINER
```

```console:コンテナ内
echo demoCA/demoCA.crt >>/etc/ca-certificates.conf
update-ca-certificates
```

## SSSDの設定

引き続きコンテナ内で操作します。

```console:コンテナ内
cat <<___ >/etc/sssd/sssd.conf
[sssd]
config_file_version = 2
domains = home

[domain/home]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldaps://primary.home
cache_credentials = True
ldap_search_base = dc=home
ldap_default_bind_dn = cn=readonly,dc=home
___
chmod 600 /etc/sssd/sssd.conf
```

```console:コンテナ内
sss_obfuscate -d home
```

ここで要求に従いパスワードを入力します。
パスワードを入力したら、SSSDサービスを開始します。

```console:コンテナ内
systemctl start sssd
```

ホームディレクトリの自動作成を有効にします。

```console:コンテナ内
pam-auth-update --enable mkhomedir
```

これでコンテナ内の操作は終了です。

```console:コンテナ内
exit
```

# 動作確認1・未ログインユーザーにメールが届いた場合

ホームディレクトリが無いユーザーにメールが届いたらどうなるか、ですね。Internal error が発生します。実験してみましょう。

[以前の設定](https://qiita.com/katz_engineer/items/d576dddbe6a4d8b65fca#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E7%99%BB%E9%8C%B2-1)の通りならば、LDAPにはユーザー名```taro```が登録されています。そして今の段階ではまだログインしていません。

LMTPコマンドを手で入力して、反応を1つずつ確認してみます。

```console:ホスト側
nc -C $HOST_IPADDR $LMTP_PORT
```

```console:nc
lhlo primary.home
```

```console:nc
mail from:<test@sender.home>
```

```console:nc
rcpt to:<taro@home>
```

```console:nc
data
From: test@sender.home
To: $USERNAME@primary.home
Subject: test

hello, world
.
```

下記のような結果になります。

```console:ホスト側
$ nc -C $HOST_IPADDR $LMTP_PORT
220 dovecot1 Dovecot (Ubuntu) ready.
lhlo primary.home
250-dovecot1
250-8BITMIME
250-CHUNKING
250-ENHANCEDSTATUSCODES
250-PIPELINING
250 STARTTLS
mail from:<test@sender.home>
250 2.1.0 OK
rcpt to:<taro@home>
250 2.1.5 OK
data
From: test@sender.home
To354 OK
: $USERNAME@primary.home
Subject: test

hello, world
.
451 4.2.0 <taro@home> Internal error occurred. Refer to server log for more information. [2023-11-15 13:23:58]
```

つまり、メール本文の入力が終わった時点で Internal error ですね。

この時のエラーログを見てみましょう。

```console:ホスト側
lxc exec $CONTAINER cat /var/log/mail.err
```

下記のような感じで、```/home/taro/Maildir```ディレクトリを作ろうとして権限不足で失敗しています。うむ、想定内ですな。

```console:ホスト側
$ lxc exec $CONTAINER cat /var/log/mail.err
Nov 15 04:23:58 dovecot1 dovecot: lmtp(taro)<8235><tsplGMBHVGUrIAAA9sUnBg>: Error: lmtp-server: conn 172.16.1.2:60146 [1]: rcpt taro@home: mkdir(/home/taro/Maildir) failed: Permission denied (euid=10000(taro) egid=10000(taro) missing +w perm: /home, dir owned by 0:0 mode=0755)
Nov 15 04:23:58 dovecot1 dovecot: lmtp(taro)<8235><tsplGMBHVGUrIAAA9sUnBg>: Error: lmtp-server: conn 172.16.1.2:60146 [1]: rcpt taro@home: mkdir(/home/taro/Maildir) failed: Permission denied (euid=10000(taro) egid=10000(taro) missing +w perm: /home, dir owned by 0:0 mode=0755)
Nov 15 04:23:58 dovecot1 dovecot: lmtp(taro)<8235><tsplGMBHVGUrIAAA9sUnBg>: Error: lmtp-server: conn 172.16.1.2:60146 [1]: rcpt taro@home: Mailbox INBOX: Failed to autocreate mailbox: Internal error occurred. Refer to server log for more information. [2023-11-15 13:23:58]
```

# LDAPユーザーでログイン

上述のエラーを踏まえ、コンテナのコンソールに接続して一度ログインします。ログインする事によってホームディレクトリも作られます。

```console:ホスト側
USERNAME=taro
```

下記のコマンドならば```taro```のパスワード入力は省略できます。

```console:ホスト側
lxc exec $CONTAINER -- login -f $USERNAME
```

特にする事も無いので、確認できたらexitしましょう。

残念ながらloginコマンドはコンソールからの入出力しか受け付けないようです。exitは手で入力しなければなりません。

```console:コンテナ内
exit
```

# 動作確認2・ログイン済ユーザーにメールお届け

LDAPユーザー```taro```のホームディレクトリが作成されたら、dovecotでメール配送できる筈
です。

```taro```宛にメールを送信します。成功する事を期待して、LMTPコマンドを一気に流します。

```console:ホスト側
nc -C $HOST_IPADDR $LMTP_PORT <<___
lhlo primary.home
mail from:<test@sender.home>
rcpt to:<$USERNAME@home>
data
From: test@sender.home
To: $USERNAME@home
Subject: test

hello, world
.
quit
___
```

メールが正しく届いていれば、LDAPユーザー```taro```に対してIMAPSで確認できる筈です。

```console:ホスト側
openssl s_client -connect $HOST_IPADDR:$IMAPS_PORT
```

```console:openssl
1 login taro Ma9H%6jg
2 list "" *
3 select INBOX
4 fetch 1 body[]
5 logout
```

# 動作確認3・存在しないユーザーにメールが届いた場合

登録されていないユーザーとして、例えば```hanako```宛のメールが届いた場合の挙動です。

今回もまたLMTPコマンドを手で入力して、反応を1つずつ確認してみます。

```console:ホスト側
nc -C $HOST_IPADDR $LMTP_PORT
```

```console:nc
lhlo primary.home
```

```console:nc
mail from:<test@sender.home>
```

```console:nc
rcpt to:<hanako@home>
```

今回は```rcpt to```の段階でエラーが発生します。

```console:nc
$ nc -C $HOST_IPADDR $LMTP_PORT
220 dovecot1 Dovecot (Ubuntu) ready.
lhlo primary.home
250-dovecot1
250-8BITMIME
250-CHUNKING
250-ENHANCEDSTATUSCODES
250-PIPELINING
250 STARTTLS
mail from:<test@sender.home>
250 2.1.0 OK
rcpt to:<hanako@home>
550 5.1.1 <hanako@home> User doesn't exist: hanako@home
```

# 仕舞い

メールサーバーの内MDAとしてDovecotを立て、SSSDと連携させました。そして少々詳しく動作確認しました。