Azure/Ubuntu にインストールした postfix / dovecot を Maildir 対応にする

「Azure で postfix のインストールを試す」
https://qiita.com/nanbuwks/items/38b54aaf8c3ecfdbb3cc
「Azure で Dovecot をインストール、telnet でメールを読む」
https://qiita.com/nanbuwks/items/c3f180fc44fa08f91820

では最低限のインストールと確認を行いました。

ここで、メール受信してメールサーバに蓄えられているメールメッセージの形式は、mailbox (mbox) 形式と Maildir 形式があります。以下の特徴があります

## mailbox (mbox) 形式

- レガシーな形式
- ユーザごとにすべてのメールが1ファイルになっている
- 対障害性低
- メールスプールディレクトリ (ex., /var/mail や /var/spool/mail ) に1ユーザ1ファイルで保管 
- メールをサーバに大量に保管している場合はパフォーマンスが低下

## Maildir 形式

- メール1通ごとに1ファイル
- ユーザごとのディレクトリ ~/Maildir 内 cur,new,tmp ディレクトリに既読、未読、配送中メールが保管される
- 対障害性Up
- メールをサーバに大量に保管しても mailbox 形式よりパフォーマンスの低下が抑えられる


デフォルトでは mailbox 形式でインストールされていますが、Maildir 形式に変更します。

# 準備

```
azureuser@test-azure-gen:~$ sudo systemctl stop postfix
azureuser@test-azure-gen:~$ sudo systemctl stop dovecot
```

mbox2maildir というユーティリティが mailscripts に含まれているのでそれをインストールしておきます。

```
azureuser@test-azure-gen:~$ sudo apt install mailscripts
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  build-essential cpp cpp-11 dctrl-tools devscripts dh-elpa-helper diffstat dpkg-dev dput elpa-notmuch emacsen-common fakeroot
  fontconfig-config fonts-dejavu-core g++ g++-11 gcc gcc-11 gcc-11-base gettext intltool-debian javascript-common
  libalgorithm-diff-perl libalgorithm-diff-xs-perl libalgorithm-merge-perl libaliased-perl libapt-pkg-perl libarchive-zip-perl
  libarray-intspan-perl libasan6 libatomic1 libauthen-sasl-perl libb-hooks-endofscope-perl libb-hooks-op-check-perl
  libc-dev-bin libc-devtools libc6-dev libcapture-tiny-perl libcc1-0 libclass-data-inheritable-perl
  libclass-method-modifiers-perl libclass-xsaccessor-perl libclone-perl libcommon-sense-perl libconfig-tiny-perl
  libconst-fast-perl libcontextual-return-perl libcpanel-json-xs-perl libcrypt-dev libdata-dpath-perl libdata-dump-perl
  libdata-messagepack-perl libdata-optlist-perl libdata-validate-domain-perl libdata-validate-ip-perl libdata-validate-uri-perl
  libdeflate0 libdevel-callchecker-perl libdevel-globaldestruction-perl libdevel-size-perl libdevel-stacktrace-perl
  libdistro-info-perl libdpkg-perl libdynaloader-functions-perl libemail-address-xs-perl libencode-locale-perl
  libexception-class-perl libexporter-tiny-perl libfakeroot libfile-basedir-perl libfile-chdir-perl libfile-dirlist-perl
  libfile-fcntllock-perl libfile-find-rule-perl libfile-homedir-perl libfile-listing-perl libfile-remove-perl
  libfile-touch-perl libfile-which-perl libfont-afm-perl libfont-ttf-perl libfontconfig1 libgcc-11-dev libgd3
  libgit-wrapper-perl libgitlab-api-v4-perl libgmime-3.0-0 libgomp1 libhash-fieldhash-perl libhtml-form-perl
  libhtml-format-perl libhtml-html5-entities-perl libhtml-parser-perl libhtml-tagset-perl libhtml-tree-perl
  libhttp-cookies-perl libhttp-daemon-perl libhttp-date-perl libhttp-message-perl libhttp-negotiate-perl
  libhttp-tiny-multipart-perl libimport-into-perl libio-html-perl libio-interactive-perl libio-prompt-tiny-perl
  libio-prompter-perl libio-pty-perl libio-socket-ssl-perl libio-string-perl libio-stringy-perl libipc-run-perl
  libipc-run3-perl libipc-system-simple-perl libisl23 libiterator-perl libiterator-util-perl libitm1 libjbig0 libjpeg-turbo8
  libjpeg8 libjs-jquery libjs-sphinxdoc libjs-underscore libjson-maybexs-perl libjson-perl libjson-xs-perl liblist-compare-perl
  liblist-moreutils-perl liblist-moreutils-xs-perl liblist-someutils-perl liblist-someutils-xs-perl liblist-utilsby-perl
  liblog-any-adapter-screen-perl liblog-any-perl liblsan0 liblwp-mediatypes-perl liblwp-protocol-https-perl libmail-box-perl
  libmail-message-perl libmail-transport-perl libmailtools-perl libmarkdown2 libmath-base85-perl libmime-types-perl
  libmodule-implementation-perl libmodule-runtime-perl libmoo-perl libmoox-aliases-perl libmoox-struct-perl libmouse-perl
  libmpc3 libnamespace-autoclean-perl libnamespace-clean-perl libnet-domain-tld-perl libnet-http-perl libnet-ipv6addr-perl
  libnet-netmask-perl libnet-smtp-ssl-perl libnet-ssleay-perl libnetaddr-ip-perl libnotmuch5 libnsl-dev libnumber-compare-perl
  libobject-id-perl libobject-realize-later-perl libpackage-stash-perl libpackage-stash-xs-perl libparams-classify-perl
  libparams-util-perl libpath-iterator-rule-perl libpath-tiny-perl libperlio-gzip-perl libperlio-utf8-strict-perl
  libpod-constants-perl libpod-parser-perl libquadmath0 libre-engine-re2-perl libre2-9 libreadonly-perl libref-util-perl
  libref-util-xs-perl libregexp-pattern-license-perl libregexp-pattern-perl librole-tiny-perl libsereal-decoder-perl
  libsereal-encoder-perl libset-intspan-perl libsexp1 libsocket6-perl libsort-versions-perl libstdc++-11-dev libstrictures-perl
  libstring-copyright-perl libstring-escape-perl libstring-shellquote-perl libsub-exporter-perl
  libsub-exporter-progressive-perl libsub-identify-perl libsub-install-perl libsub-name-perl libsub-quote-perl
  libsyntax-keyword-try-perl libtext-glob-perl libtext-levenshteinxs-perl libtext-markdown-discount-perl libtext-xslate-perl
  libtiff5 libtime-duration-perl libtime-moment-perl libtimedate-perl libtirpc-dev libtry-tiny-perl libtsan0 libtype-tiny-perl
  libtype-tiny-xs-perl libtypes-serialiser-perl libubsan1 libunicode-utf8-perl liburi-perl libuser-identity-perl
  libvariable-magic-perl libwant-perl libwebp7 libwww-perl libwww-robotrules-perl libxapian30 libxml-libxml-perl
  libxml-namespacesupport-perl libxml-parser-perl libxml-sax-base-perl libxml-sax-expat-perl libxml-sax-perl libxpm4
  libxs-parse-keyword-perl libyaml-libyaml-perl licensecheck lintian linux-libc-dev lto-disabled-list lzip lzop make
  manpages-dev notmuch patchutils perl-openssl-defaults python3-argcomplete python3-decorator python3-gpg python3-gssapi
  python3-pgpy python3-pgpy-doc python3-unidiff python3-xdg rpcsvc-proto t1utils unzip wdiff
Suggested packages:
  cpp-doc gcc-11-locales debtags adequate at autopkgtest bls-standalone check-all-the-things cvs-buildpackage debhelper
  diffoscope disorderfs dose-extra duck elpa-devscripts faketime gnuplot how-can-i-help libdbd-pg-perl
.
.
.
0 upgraded, 259 newly installed, 0 to remove and 4 not upgraded.
Need to get 78.1 MB of archives.
After this operation, 257 MB of additional disk space will be used.
Do you want to continue? [Y/n] 
```

おっ結構使いますね・・・このままインストールしてしまいます。


# メールスプールの変換

メールスプールを確認します。
```
azureuser@test-azure-gen:~$ ls -alh /var/mail
total 16K
drwxrwsr-x  2 root      mail 4.0K Apr  9 07:09 .
drwxr-xr-x 14 root      root 4.0K Apr  7 14:36 ..
-rw-------  1 azureuser mail    0 Apr  8 06:13 azureuser
-rw-------  1 example   mail  849 Apr  9 05:38 example
-rw-------  1 root      mail 1.2K Apr  9 04:48 root
```
ユーザごとに mailbox ファイルが作られています。このユーザごとに変換作業を行います。
まず azureuser から。
```

azureuser@test-azure-gen:~$ mbox2maildir /var/mail/azureuser ~/Mailbox
azureuser@test-azure-gen:~$ ls ~/Mailbox
cur  new  tmp
```
次に root 。
```
azureuser@test-azure-gen:~$ sudo bash -
root@test-azure-gen:/home/azureuser# mbox2maildir /var/mail/user ~/Mailbox
root@test-azure-gen:/home/azureuser# ls /root
Mailbox  snap
```
最後に example ユーザ。
```
root@test-azure-gen:/home/azureuser# su - example
example@test-azure-gen:~$ mbox2maildir /var/mail/example ~/Mailbox
example@test-azure-gen:~$ ls -alh
total 44K
drwxr-x--x 5 example example 4.0K Apr  9 07:09 .
drwxr-xr-x 4 root    root    4.0K Apr  8 04:09 ..
-rw------- 1 example example  335 Apr  9 04:48 .bash_history
-rw-r--r-- 1 example example  220 Apr  8 04:09 .bash_logout
-rw-r--r-- 1 example example 3.7K Apr  8 04:09 .bashrc
-rw-r--r-- 1 example example  807 Apr  8 04:09 .profile
-rw------- 1 example example 1.3K Apr  8 04:17 .viminfo
drwx------ 5 example example 4.0K Apr  9 07:09 Mailbox
drwx------ 3 example example 4.0K Apr  9 05:34 mail
-rw------- 1 example example  817 Apr  8 06:14 mbox
drwxrwxr-x 2 example example 4.0K Apr  8 04:17 public_html
example@test-azure-gen:~$ exit
logout

```



# 変換を確認


先の実験
「Azure で Dovecot をインストール、telnet でメールを読む」
https://qiita.com/nanbuwks/items/c3f180fc44fa08f91820
で、 example ユーザにメールデータが残っている状態だったので、/home/example/Mailbox の変換結果に反映しているかどうか確認してみます。


```
root@test-azure-gen:/home/azureuser# ls /home/example/Mailbox
cur  new  tmp
root@test-azure-gen:/home/azureuser# ls /home/example/Mailbox/new
1681024194.M840509P34975Q1.test-azure-gen
root@test-azure-gen:/home/azureuser# cat /home/example/Mailbox/new/1681024194.M840509P34975Q1.test-azure-gen 
Return-Path: <azureuser@test-azure-gen.d5vkxbztwwse1dua0e2ybepgyg.phxx.internal.cloudapp.net>
X-Original-To: example
Delivered-To: example@test-azure-gen.d5vkxbztwwse1dua0e2ybepgyg.phxx.internal.cloudapp.net
Received: by test-azure-gen.d5vkxbztwwse1dua0e2ybepgyg.phxx.internal.cloudapp.net (Postfix, from userid 1000)
	id 646BD3F9B3; Sun,  9 Apr 2023 05:36:36 +0000 (UTC)
Message-Id: <20230409053636.646BD3F9B3@test-azure-gen.d5vkxbztwwse1dua0e2ybepgyg.phxx.internal.cloudapp.net>
Date: Sun,  9 Apr 2023 05:36:36 +0000 (UTC)
From: Ubuntu <azureuser@test-azure-gen.d5vkxbztwwse1dua0e2ybepgyg.phxx.internal.cloudapp.net>
X-IMAPbase: 1681018471 0000000001
X-UID: 1                                                  
Status: RO

Hello Dovecot

```
無事変換できているみたいです。


# 他のユーザの Maildir を作る
(2023/7/10 追記：)

上記以外のユーザの Maildir を作ります。
それぞれのユーザ権限で、以下のように作業をします。
 

```
$ mkdir ~/Maildir
$ chmod 700 ~/Maildir
```

また、新規ユーザに自動で Maildir が作られるように、以下のように設定しておきます。

```
$ sudo mkdir /etc/skel/Maildir
$ sudo chmod 700 /etc/skel/Maildir
```

# postfix 設定変更

postfix の設定は、`/etc/postfix/main.cf ` で行いますが、
「Azure で postfix のインストールを試す」
https://qiita.com/nanbuwks/items/38b54aaf8c3ecfdbb3cc
でインストールした状態では、`/etc/postfix/main.cf ` は作られていません。

`/etc/postfix/main.cf ` が無かったり、あるけどそこで指定された項目以外の設定値は、デフォルト値が用いられます(デフォルト値は` postconf -d ` コマンドで確認できます)。

デフォルト値では、postfix は mailbox を使う設定になっているので Maildir を使う設定だけを書き込んだ `/etc/postfix/main.cf `  を作成します。

以下の内容で `/etc/postfix/main.cf` を作成します。

```
home_mailbox = Maildir/
setgid_group = postdrop
```

なお、設定値については `/etc/postfix/main.cf.proto ` に詳細が説明されています。
```

# DELIVERY TO MAILBOX
#
# The home_mailbox parameter specifies the optional pathname of a
# mailbox file relative to a user's home directory. The default
# mailbox file is /var/spool/mail/user or /var/mail/user.  Specify
# "Maildir/" for qmail-style delivery (the / is required).
#
#home_mailbox = Mailbox
#home_mailbox = Maildir/
.
.
.
 
```
設定変更を反映します
```
azureuser@test-azure-gen:~$ sudo systemctl start postfix
```

## postfix の動作確認

メールを送ります。
```
azureuser@test-azure-gen:~$ echo "Hello Maildir" | sendmail example
```
example に配送されているでしょうか?
```
azureuser@test-azure-gen:~$ ls /home/example/Maildir/new
ls: cannot access '/home/example/Maildir/new': Permission denied
```

エラーが出るので、sudo をつけてメールスプールを読みます。
```

azureuser@test-azure-gen:~$ sudo ls  /home/example/Maildir/new
1681026269.V801I3fda9M274685.test-azure-gen
azureuser@test-azure-gen:~$ sudo cat  /home/example/Maildir/new/1681026269.V801I3fda9M274685.test-azure-gen
Return-Path: <azureuser@test-azure-gen.localdomain>
X-Original-To: example
Delivered-To: example@test-azure-gen.localdomain
Received: by test-azure-gen.localdomain (Postfix, from userid 1000)
	id 3B55E3FDA7; Sun,  9 Apr 2023 07:44:29 +0000 (UTC)
Message-Id: <20230409074429.3B55E3FDA7@test-azure-gen.localdomain>
Date: Sun,  9 Apr 2023 07:44:29 +0000 (UTC)
From: Ubuntu <azureuser@test-azure-gen.localdomain>

Hello Maildir
```
さて、先に azureuser だと エラーが出ました。これは Maildir がユーザーディレクトリ内にあるので、適切な権限がないとメールスプールファイルが読めないからです。
main.cf に設定した `setgid_group = postdrop` の項目は、 postfix が Maildir にアクセスするときに使用するアクセス権を持つためのものです。

# dovecot 設定変更
`/etc/dovecot/conf.d/10-mail.conf ` の冒頭部分

```
##
## Mailbox locations and namespaces
##

# Location for users' mailboxes. The default is empty, which means that Dovecot
# tries to find the mailboxes automatically. This won't work if the user
# doesn't yet have any mail, so you should explicitly tell Dovecot the full
# location.
#
# If you're using mbox, giving a path to the INBOX file (eg. /var/mail/%u)
# isn't enough. You'll also need to tell Dovecot where the other mailboxes are
# kept. This is called the "root mail directory", and it must be the first
# path given in the mail_location setting.
#
# There are a few special variables you can use, eg.:
#
#   %u - username
#   %n - user part in user@domain, same as %u if there's no domain
#   %d - domain part in user@domain, empty if there's no domain
#   %h - home directory
#
# See doc/wiki/Variables.txt for full list. Some examples:
#
#   mail_location = maildir:~/Maildir
#   mail_location = mbox:~/mail:INBOX=/var/mail/%u
#   mail_location = mbox:/var/mail/%d/%1n/%n:INDEX=/var/indexes/%d/%1n/%n
#
# <doc/wiki/MailLocation.txt>
#
mail_location = mbox:~/mail:INBOX=/var/mail/%u
```
この、`mail_location` の項目を以下のように変更します。

```
mail_location = maildir:~/Maildir

```
変更を反映します。
```
azureuser@test-azure-gen:~$ sudo systemctl start dovecot
```

なお、postfix の設定では Maildir にアクセスする gid の設定も行う必要がありました。 
 dovecot の場合同じファイルには以下のようにあるのですが、これは最初から有効になっているようです。

```
# Group to enable temporarily for privileged operations. Currently this is
# used only with INBOX when either its initial creation or dotlocking fails.
# Typically this is set to "mail" to give access to /var/mail.
mail_privileged_group = mail

```
## dovecpt の 動作確認

先の実験と同様に手元のPCから telnet でアクセスします。

```
$ telnet example.com 110
Trying 123.456.789.012...
Connected to example.com.
Escape character is '^]'.
+OK Dovecot (Ubuntu) ready.
user example
+OK
PASS hogehogefugafuga
+OK Logged in.
LIST
+OK 1 messages:
1 434
.
RETR 1
+OK 434 octets
Return-Path: <azureuser@test-azure-gen.localdomain>
X-Original-To: example
Delivered-To: example@test-azure-gen.localdomain
Received: by test-azure-gen.localdomain (Postfix, from userid 1000)
	id 3B55E3FDA7; Sun,  9 Apr 2023 07:44:29 +0000 (UTC)
Message-Id: <20230409074429.3B55E3FDA7@test-azure-gen.localdomain>
Date: Sun,  9 Apr 2023 07:44:29 +0000 (UTC)
From: Ubuntu <azureuser@test-azure-gen.localdomain>

Hello Maildir
.
```

うまく読めていますね。今回はこのメールを削除して、Maildir へのアクセス権がちゃんと設定できているかどうか確認してみます。

```
DELE 1
+OK Marked to be deleted.
QUIT
+OK Logging out, messages deleted.
Connection closed by foreign host.
```
再度アクセス
```
$ telnet example.com 110
Trying 123.456.789.123...
Connected to example.com.
Escape character is '^]'.
+OK Dovecot (Ubuntu) ready.
USER example
+OK
PASS hogehogefugafuga
+OK Logged in.
LIST
+OK 0 messages:
.
QUIT
+OK Logging out.
Connection closed by foreign host.
```

先程読んだメールは削除されていました。設定はうまくいっているようです。


# mail コマンドを Maildir に対応させる

(2023/07/09 追記)

上記のように telnet で Dovecot にアクセスするとメールが読めましたが、ローカルで mail コマンドを使うと届いたメールが読めなくなってました。

```
$ mail
No mail for example
```


調べてみると、歴史的な本来の mail コマンドは、いまどきは mailx というプログラムに置き換わっていて、 mail コマンドを呼ぶと mailx が動作するらしい。そして mailx はいくつもの実装があるらしい。


- bsd-mailx
- heirloom-mailx 
- GNU Mailutils

そうして以前は Maildir 形式をサポートしたりしていなかったりだったらしいが・・・ 今使用している mailx は何だろう?

```
$ which mail
/usr/bin/mail
$ ls -alh /usr/bin/mail
lrwxrwxrwx 1 root root 22 Jul  2 05:01 /usr/bin/mail -> /etc/alternatives/mail
$ mail --version
mail (GNU Mailutils) 3.7
Copyright (C) 2007-2019 Free Software Foundation, inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
```

GNU Mailutils が入っているらしいです。なお、以下のようにして確かめてみたらその他の mailx のパッケージは用意されているようですが・・・

```
$ apt-cache search bsd-mailx
bsd-mailx - simple mail user agent
devscripts - scripts to make the life of a Debian Package maintainer easier
$ apt-cache search heirloom-mailx
t-prot - display filter for Internet messages

```
以下の設定ではスイッチできない模様。

```
$ sudo update-alternatives --config mailx
There is only one alternative in link group mailx (providing /usr/bin/mailx): /usr/bin/mail.mailutils
Nothing to configure.

```

さて、この GNU Mailutils ですが、以下のように環境変数を設定したら Maildir が読めるようです。

```
$ MAIL=~/Maildir
$ echo $MAIL
/home/example/Maildir
```

確認してみます。

```
$ mail
"/home/example/Maildir": 5 messages 5 unread
>U   1 Ubuntu                               10/634   
 U   2 Ubuntu                               10/632   
 U   3 Ubuntu                               10/634   
 U   4 azureuser@test-azu                   11/670   
 U   5 Ubuntu                               10/634   
? q
Held 5 messages in /home/example/Maildir

```

読めるようになりました

全員に反映するには、以下の一行を /etc/profile の末尾に足しておきます。

```
MAIL=~/Maildir
```