2022/12/29(木)メールサーバの中規模改修と基礎知識(7)~ postfix

Postfix-logo.png

postfix は、sendmail の代替を目指す、電子メール送受信プログラムです。
記事公開日時点では、3.7.3 が最新バージョンです。

Postfix のインストール

機能説明等は不要と思いますので、早速インストール手順を示してみます。
インストール作業は、全て root ユーザで行います。
Postfix をインストールする場合、FreeBSD13 では、下記のモジュールが事前に必要です。(但し、2022/12/24 現在)
これらの多くは、Ports や Package でインストールしても、管理上特段問題にはなりません。
 ・perl5-5.36.0 	 (Ports から カテゴリ:lang)
 ・ca_root_nss-3.86	 (Ports から カテゴリ:security)
 ・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 以上	 (これはソースコードからの構築を強く推奨)
 ・dovecot 2.3.19.1 以上 (本記事シリーズでソースコードから構築)
 ・gmake-4.3_2		 (Ports から カテゴリ:devel)
 ・icu-72.1,1		 (Ports から カテゴリ:devel)
上記をインストール出来ていたら、先ず、運用時における Postfix 自体が意図しているセキュリティポリシーに合わせるため、ここで専用のユーザを予めvipw や useradd コマンドで作っておきます。
vipw の場合は、編集画面で、下記の行を追加しておきます:
postfix::2000:2000::0:0:postfix:nonexistent:/usr/sbin/nologin
vipw でユーザ追加した場合は、/etc/group ファイルに下記の行を追加しておきます:
postdrop:*:65001:
更に、/etc/group ファイル内の該当行を下記のように変更しておきます:
mailuser:*:3000:opendkim,milter-manager,postfix
ユーザID、グループIDは、上記例にこだわる必要はありませんが、当然のことながら、ユーザID・グループIDが他と重複しないように注意です。
更に vipw でユーザ追加した場合は、つまらないセキュリティホールを作らないために、下記コマンドも一応実行しておきます:
# passwd postfix
今度は以下の手順に沿ってインストール作業を行います。Postfix のインストールは configure を使わない独特で且つ汎用的に対応できる形態になっています。
# cp postfix-3.7.3.tar.gz /usr/local/src	 
# cd /usr/local/src	 
# tar xvzf postfix-3.7.3.tar.gz	 
# cd postfix-3.7.3	 
 	 
# setenv CPPFLAGS '-I/usr/local/include -I/usr/include' 	(configure が上手くライブラリを探せないため)
# setenv LDFLAGS '-L/usr/local/lib -L/usr/lib'			(configure が上手くライブラリを探せないため)
# unsetenv LD_LIBRARY_PATH' 	 (3.7.x からは、LD_LIBRARY_PATH が設定されているとコンパイルを受け付けない)

※画面上では、説明のために改行していますが、 make の部分は、改行せずに、半角スペースで区切って、一気に入力。
# make -f Makefile.init makefiles 			 (OS等を自動識別してコンパイル)
 'CCARGS=-DHAS_LDAP -DUSE_TLS -DUSE_SASL_AUTH 		 (LDAP,SSL,SASL認証をサポートする指示)
     -DDEF_SASL_SERVER=\"dovecot\" 		 (dovecot SASL を使用する指示 ※2.3.x 以降で必須)
     -DDEF_COMMAND_DIR=\"/usr/local/sbin\" 	 (postfix 各種実行プログラムの格納ディレクトリ)
     -DDEF_CONFIG_DIR=\"/usr/local/etc/postfix\" 	 (postfix 各種設定ファイル格納ディレクトリ)
     -DDEF_DAEMON_DIR=\"/usr/local/libexec/postfix\" (postfix 各種デーモンプログラムの格納ディレクトリ)
     -I/usr/local/include	 
     -I/usr/include/openssl' 			 (openssl のヘッダファイル)
 'AUXLIBS=-L/usr/lib -lssl -lcrypto 			 (SSL ライブラリのある場所)
      -L/usr/local/lib -lldap -llber -licudata -licui18n -licuio -licutest -licutu -licuuc'
  							 (LDAP・icu ライブラリのある場所)
postfix の場合、いわゆる「メール送信」だけを行う Null Client な構成だと、上記 make コマンドに与えるパラメータが異なってくるのですが、今回は、普通のメールサーバの構築を行うため、提示のオプションになります。

続いて、
# make
で、エラーが出ないで終了したことを確認したら、
# make install
で、インストールを実施します。CUIインタフェースですが、対話形式のインストーラとなっています。
[ ] 内で示す文字列は、デフォルト値または、識別している値です。
変更しない場合は、そのままリターンキー押下、変更する場合に設定値を入力してリターンキー押下で次の項目に進みます。
※下記のように設問に対応する。(設問順序は、必ずしもこの順番にはなりません)
Please specify the prefix for installed file names. This is useful
if you are building ready-to-install packages for distribution to
other machines.
install_root: [/]

Please specify a directory for scratch files while installing
Postfix. You must have write permission in this directory.
tempdir: [/usr/local/src/postfix-3.7.3] /tmp

Please specify the destination directory for installed Postfix
configuration files.
config_directory: [/usr/local/etc/postfix]

Please specify the destination directory for installed Postfix
administrative commands. This directory should be in the command
search path of adminstrative users.
command_directory: [/usr/local/sbin]

Please specify the destination directory for installed Postfix
daemon programs. This directory should not be in the command search
path of any users.
daemon_directory: [/usr/local/libexec/postfix]

Please specify the final destination directory for Postfix-writable
data files such as caches or random numbers. This directory should
not be shared with non-Postfix software.
data_directory: [/var/lib/postfix] /var/tmp/postfix

Please specify the destination directory for the Postfix HTML files.
Specify "no" if you do not want to install these files.
html_directory: [no]

Please specify the owner of the Postfix queue. Specify an account
with numerical user ID and group ID values that are not used by any
other accounts on the system.
mail_owner: [postfix]

Please specify the full destination pathname for the installed
Postfix mailq command. This is the Sendmail-compatible mail queue
listing command.
mailq_path: [/usr/bin/mailq] /usr/local/libexec/postfix/mailq

Please specify the destination directory for the Postfix on-line
manual pages. You can no longer specify "no" here.
manpage_directory: [/usr/local/man]

Please specify the full destination pathname for the installed
Postfix newaliases command. This is the Sendmail-compatible command
to build alias databases for the Postfix local delivery agent.
newaliases_path: [/usr/bin/newaliases] /usr/local/libexec/postfix/newaliases

Please specify the destination directory for Postfix queues.
queue_directory: [/var/spool/postfix]

Please specify the destination directory for the Postfix README
files. Specify "no" if you do not want to install these files.
readme_directory: [no] /usr/local/etc/postfix/docs

Please specify the full destination pathname for the installed
Postfix sendmail command. This is the Sendmail-compatible mail
posting interface.
sendmail_path: [/usr/sbin/sendmail] /usr/local/libexec/postfix/sendmail

Please specify the group for mail submission and for queue management
commands. Specify a group name with a numerical group ID that is
not shared with other accounts, not even with the Postfix mail_owner
account. You can no longer specify "no" here.
setgid_group: [postdrop]

Please specify the final destination directory for Postfix
shared-library files.
shlib_directory: [no] 

Please specify the final destination directory for non-executable
files that are shared among multiple Postfix instances, such as
postfix-files, dynamicmaps.cf, as well as the multi-instance template
files main.cf.proto and master.cf.proto.
meta_directory: [/usr/local//etc/postfix] 
もし間違えたら、修正手段は容易されていないので、Ctrl+C で中断し再度
# make install
を行います。

次に、FreeBSD に特化した設定内容として、 /etc/mail/mailer.conf の設定変更を行います。
このファイルの内容を下記のように変更します:
# $FreeBSD: src/etc/mail/mailer.conf,v 1.3 2002/04/05 04:25:12 gshapiro Exp $
#
# Execute the "real" sendmail program, named /usr/libexec/sendmail/sendmail
#
sendmail    /usr/local/libexec/postfix/sendmail
send-mail    /usr/local/libexec/postfix/sendmail
mailq      /usr/local/libexec/postfix/sendmail
newaliases   /usr/local/libexec/postfix/sendmail
hoststat    /usr/local/libexec/postfix/sendmail
# purgestat   /usr/local/libexec/senamail/sendmail

Postfix の設定

○ サーバ証明書の取得とセットアップ
Let's Encrypt の certbot を使用する前提でここに手順を記述しています。
※ 操作するサーバ上で Webサーバが稼動している場合は、必ず一旦停止させること。
 
# certbot certonly --standalone --agree-tos -m user@example.com -d smtp.example.com
# certbot certonly --standalone --agree-tos -m user@example.com -d smtp.example.net
 
# cd /usr/local/etc/postfix
# vi tls_sni.map
tls_sni.map は、上記例をもとにすると、下記内容のように記述します:
※複数行の表示になっているが、
1ドメイン=1行で [対象ドメイン名] [秘密鍵ファイル名] [CA・公開鍵結合ファイル名] の順に、
半角スペースで区切って入力してください。
smtp.example.com /usr/local/etc/letsencrypt/live/smtp.example.com/privkey.pem /usr/local/etc/letsencrypt/live/smtp.example.com/fullchain.pem
smtp.example.net /usr/local/etc/letsencrypt/live/smtp.example.net/privkey.pem /usr/local/etc/letsencrypt/live/smtp.example.net/fullchain.pem 
tls_sni.map の編集を行ったら、
# postmap -F hash:/usr/local/etc/postfix/tls_sni.map
で、 Postfix で処理できるデータベース形式に変換しておきます(tls_sni.map.db が新たに生成されます)。

○ main.cf の設定変更
次に、/usr/local/etc/postfix/main.cf を下記の要領で編集します:
# cd /usr/local/etc/postfix
# cp main.cf main.cf.org
# vi main.cf
※ 最低限の変更部分のみ抜粋:
compatibility_level = 3.6                #挙動の設定
inet_protocols  = all                  #IPv4,IPv6 両方使用
inet_interfaces = all
unknown_address_reject_code = 550

tls_server_sni_maps = hash:/usr/local/etc/postfix/tls_sni.map     #TLS対象ホスト名
smtpd_tls_CApath = /etc/ssl/carts                   #接続相手先のCAルート証明書情報
smtpd_tls_chain_files =
 /usr/local/etc/letsencrypt/live/pmx1.admin-plus.net/privkey.pem,
 /usr/local/etc/letsencrypt/live/pmx1.admin-plus.net/fullchain.pem  #デフォルト適用の証明書情報

smtpd_tls_received_header = yes
smtpd_tls_session_cache_database = hash:/var/tmp/postfix/smtpd_scache #キャッシュデータベース
smtpd_tls_loglevel = 1                         #ログレベルの設定(必要に応じ 1 ~ 3 )
smtpd_tls_security_level = may                     #TLSサポートレベルの設定
smtpd_sasl_auth_enable = yes                      #SASL による SMTP-AUTHサポート
smtpd_sasl_type = dovecot                       #SASL認証機構の指定
smtpd_sasl_path = private/auth                     #SASL認証ソケットの指定

smtpd_sender_restrictions = reject_unknown_sender_domain
smtpd_relay_restrictions =
 permit_mynetworks,
 permit_sasl_authenticated,
 defer_unauth_destination
smtpd_recipient_restrictions =
 permit_mynetworks,
 permit_sasl_authenticated,
 reject_unauth_destination

smtp_tls_CApath = /etc/ssl/carts                    #接続クライアントのCAルート証明書情報
smtp_tls_session_cache_database = hash:/var/tmp/postfix/smtp_scache  #キャッシュデータベース
smtp_tls_security_level = may                     #TLSサポートレベルの設定
smtp_tls_loglevel = 1                         #ログレベルの設定(必要に応じ 1 ~ 3 )

milter_protocol = 6                          #milterプロトコルパージョンを6にする
milter_default_action = accept                     #milter接続失敗時のアクション
milter_mail_macros = {auth_author} {auth_type} {auth_authen}      #milter呼び出し時のパラメータ設定
smtpd_milters = unix:/var/run/milter-manager/milter-manager.sock    #SMTPD 時の milter 呼び出しアクション
non_smtpd_milters = unix:/var/run/milter-manager/milter-manager.sock  #SMTPD 以外の milter 呼び出しアクション

virtual_mailbox_maps = ldap:/usr/local/etc/postfix/ldap-mailbox.cf   #バーチャルユーザ情報
virtual_transport = dovecot                      #バーチャルユーザ配送 LDA指定
dovecot_destination_recipient_limit = 1                #同時配送数(複数メールの同時処理無し設定)
○ master.cf の設定変更
次に、/usr/local/etc/postfix/master.cf を下記の要領で編集します:
# cp master.cf master.cf.org
# vi master.cf
※ 最低限の変更部分のみ抜粋:
smtp    inet n  -  n  -   -  smtpd
smtps    inet n  -  n  -   -  smtpd
   -o smtpd_tls_wrappermode=yes
   -o smtpd_sasl_auth_enable=yes
   -o smtpd_sasl_type=dovecot
   -o smtpd_sasl_path=private/auth
   -o smtpd_sender_restrictions=reject_unknown_sender_domain
   -o smtpd_relay_restrictions=permit_mynetworks,permit_sasl_authenticated,reject_unauth_destination
   -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject_unauth_destination
   -o smtpd_tls_security_level=may

submission inet n  -  n  -   -  smtpd
   -o smtpd_tls_wrappermode=yes
   -o smtpd_sasl_auth_enable=yes
   -o smtpd_sasl_type=dovecot
   -o smtpd_sasl_path=private/auth
   -o smtpd_sender_restrictions=reject_unknown_sender_domain
   -o smtpd_relay_restrictions=permit_mynetworks,permit_sasl_authenticated,reject_unauth_destination
   -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject_unauth_destination
   -o smtpd_tls_security_level=may

tlsmgr   unix -  -  n  1000? 1  tlsmgr
dovecot   unix -  n  n  -   -  pipe
 flags=DRhu user=vmail argv=/usr/local/libexec/dovecot/deliver -f {$sender} -d ${recipient}
master.cf のこの設定で、smtp(port 25)、smtps(port 465)、submission(port 587) の待ち受けを可能にします。

最後に、aliases の設定をします。
# cd /etc/mail
# vi /etc/mail/aliases
aliases の該当行を下記のように変更しておきましょう:
root:	me@example.com
     ↓
root:	user@example.jp
要するに、実在し、受信できる電子メーるアドレスにします。
無断で他人のメールアドレスを記述したり、存在しないメールアドレスを記述してはいけません。
これを変更したら
# newaliases
として、データベースを更新しておきます。aliases.db が直近の時刻に変更されていることを ls -al コマンドなどで確認しておいてください。

Postfix の設定は以上です。

2022/12/28(水)メールサーバの中規模改修と基礎知識(6)~ milter-manager

milter-mamager は、複数のmilter インターフェースアプリケーションを管理し、postfix や sendmail 上の設定を簡略化する目的で使用される電子メール送受信アプリケーションです。
日本語の milter-manager サポートページは、2.1.5 (2019/09/10 リリース)で更新が止まっており(但し、大いに参考にはなる)、FreeBSD の Ports は 2.1.6 (2022/01/13 リリース)で更新が止まっているのですが、milter-manager の最新バージョンはどうやら 2.2.5(2022/12/12 リリース)のようです。

milter-manager の概要

今回は、下手くそ(且つ汚ない)な手書きで描いた、下記のような挙動をさせるために使います。
20221228_miltermanager.png

複数の milter アプリケーションを使用する場合、postfix 側で単純に milter の設定を行えば事足ります。
ですが、実際はたった2つでも設定がちょっと煩雑になる。

加えて、将来的に電子メールサービスの付加機能は増えていく傾向にあると思われるため、そのような状況に先回り対処する目的で開発されたのが、この milter-manager です。

milter アプリケーションは、典型的なデーモン形式の造りになっているのが通常で、ソケット通信で電子メールメッセージの授受を行いますが、TCP/IPによるソケット通信では、サーバ負荷が重くなりがちなのです。
なので、通常は unix ソケット通信による手法で代替し、今回も milter-manager とのやりとりは、全て unix ソケット通信にて動作させています。

milter-manager は、postfix からの一度の呼び出しアクションで、バックグラウンドで複数の milter アプリケーション処理を実行する役目を担います。

milter-manager のインストール準備(1)

ここからは、基本的に root アカウントでの作業となります。
milter-manager をインストールする場合、FreeBSD13 では、下記のモジュールが事前に必要です。(但し、2022/12/24 現在)
 ・perl5-5.36.0 		 (Ports から カテゴリ:lang)
 ・ruby-3.0.5,1 		 (Ports から カテゴリ:lang)
 ・libtool-2.4.7		 (Ports から カテゴリ:devel)
 ・gettext-runtime-0.21	 (Ports から カテゴリ:devel)
 ・gettext-tools-0.21_1	 (Ports から カテゴリ:devel)
 ・libev-4.33,1		 (Ports から カテゴリ:devel)
 ・gmake-4.3_2			 (Ports から カテゴリ:devel)
 ・libxml2-2.10.3_1		 (Ports から カテゴリ:textproc)
 ・glib-2.74.4_2		 (Ports から カテゴリ:devel)
 ・gobject-introspection-1.74.0,1(Ports から カテゴリ:devel)
 ・intltool-0.51.0_1		 (Ports から カテゴリ:textproc)
 ・rubygem-glib2-4.0.3		 (Ports から カテゴリ:devel)
 ・rubygem-rexml-3.2.5		 (Ports から カテゴリ:textproc)
milter-manager も結構依存するものが多いのですが、
これらの多くは、Ports や Package でインストールしても、管理上特段問題にはなりません。
ruby で動作させている部分が案外多く、Perl と、何故か gnome ライブラリの一部(glib,gobject) も依存します。
特にに、glib の古いライブラリがサーバ上に残っていると上手く動作しない不具合が確認されています。

更に、インストールに先立ち、milter-manager のセキュリティポリシーに従うため、ユーザ milter-manager を、vipw や useradd コマンドで追加します:

vipw コマンドの場合は、下記の1行を編集画面で追加します。
milter-manager::2005:3000::0:0:milter manager execute user:/home/staff/milter-manager:/usr/sbin/nologin
※ ユーザホームディレクトリを内部処理でワークエリア的に使うため、ホームディレクトリを必ず割り当てる

vipw でユーザ追加した場合、/etc/group に下記の1行を例示のように変更しておきます:
mailuser:*:3000:opendkim,milter-manager
更に vipw でユーザ追加した場合は、つまらないセキュリティホールを作らないために、下記コマンドも一応実行しておきます:
# passwd milter-manager

milter-manager のインストール準備(2)

先ず、ソースコードの展開を行います。
# cp milter-manager-2.2.5.tar.gz /usr/local/src	 
# cd /usr/local/src	 
# tar xvzf milter-manager-2.2.5.tar.gz	 
# cd milter-manager-2.2.5
実は、実際にソースコードのコンパイルを実施したところ、FreeBSDにおいては、素のソースコードではエラーが発生し、上手く出来ません。
具体的には、configure スクリプト、binding/ruby/test/run-test.sh、Makefile.in の3本の変更が必要なようです。

これらは、patch コマンドを使用するか、手入力修正して、作業実施前にソースコードの修正を行う必要があります。
ここでは、量が少ないのと、用意が面倒なので手入力で修正しまいます。

FreeBSD12/FreeBSD13 においては、以下のように修正することで、milter-manager 2.2.5 のソースコードでコンパイルできるようになります。(確認済)
binding/ruby/test/run-test.sh 1行目
 #!/bin/bash
   ↓
 #!/bin/sh

Makefile.in 516行目
        milter-manager.pc libmilter.pc $(am__append_1)
                          ↓
        milter-manager.pc $(am__append_1)

configure 15177 行目
    RUBY_GLIB2_CFLAGS="-I$ruby_glib2_gem_dir/ext/glib2"
             ↓
    RUBY_GLIB2_CFLAGS="-I$ruby_glib2_gem_dir/lib"

milter-manager のインストール

以下の手順に従ってインストールしていきます:
# setenv CPPFLAGS '-I/usr/local/include -I/usr/include'	(configure が上手くライブラリを探せないため)
# setenv LDFLAGS '-L/usr/local/lib -L/usr/lib' 		(configure が上手くライブラリを探せないため)
# unsetenv LD_LIBRARY_PATH	 
# ./configure --localstatedir=/var
configure スクリプトの実行が完了すると、以下のようなサマリーが出力されます:
Configure Result:

  Package Platform        : freebsd
  Package Options         :

  Default Effective User  :
  Default Effective Group :
  Default Socket Group    :
  Default Connection Spec : unix:/var/run/milter-manager/milter-manager.sock
  Default PID File        :

  GLib                    : 2.74.4
  libev                   : yes
  Ruby                    : /usr/local/bin/ruby
    CFLAGS                : -I/usr/local/include/ruby-3.0/amd64-freebsd13 -I/usr/local/include/ruby-3.0/
    LIBS                  : -L/usr/local/lib -fstack-protector-strong -Wl,--compress-debug-sections=zlib -Wl,-E -lruby30 -lm -lpthread -L/usr/local/lib
  Ruby version            : ruby 3.0.5p211 (2022-11-24 revision ba5cf0f7c5) [amd64-freebsd13]
  Ruby/GLib2              : -I/usr/local/lib/ruby/gems/3.0/gems/glib2-4.0.3/lib
  Ruby milter PATH        :

  rd2                     : rd2-not-found
  GTK-Doc                 : no

  Cutter                  :
  LCOV                    :
  coverage                :
  coverage report by LCOV :

  Cutter Source Path      : $(top_srcdir)/vendor/cutter-source

  Launchpad PGP key       :
  GPG UID                 : 3F09D1EA68E5F18B4EC8FEEAFF2030C057B9884E
続いて、下記のコマンドを順に実行し、エラーが無ければ、本体のインストールは完了です。
# gmake
# gmake install
最後に下記手順で動作環境を準備して、インストール完了です:
# mkdir /home/staff/milter-manager
# chown milter-manager:mailuser /home/staff/milter-manager
# mkdir /var/run/milter-manager
# chown milter-manager:maliuser /var/run/milter-manager

起動・停止スクリプトの設置(FreeBSD に特化している項目)

このスクリプトは一度作っておくと、バージョンアップの際に再作成の必要はありません。

○ milter-manager の起動スクリプト
/usr/local/etc/rc.d/ ディレクトリ配下に、milter-manager のファイル名で下記内容を作成します。
実行権限を与えることを忘れないようにしてください:
#!/bin/sh

# PROVIDE: milter-manager
# REQUIRE: LOGIN
# BEFORE: mail
# KEYWORD: shutdown

#
# Add the following lines to /etc/rc.conf to enable milter-manager:
#
#   milter_manager_enable=yes
#

. /etc/rc.subr

name="milter_manager"
rcvar="milter_manager_enable"
extra_commands="reload"

load_rc_config $name
: ${milter_manager_enable="NO"}
: ${milter_manager_pid_file="/var/run/milter-manager/milter-manager.pid"}
if getent passwd milter-manager > /dev/null; then
    : ${milter_manager_user_name="milter-manager"}
else
    : ${milter_manager_user_name="mailnull"}
fi
if getent group milter-manager > /dev/null; then
    : ${milter_manager_group_name="milter-manager"}
else
    : ${milter_manager_group_name="mailuser"}
fi
: ${milter_manager_socket_group_name="mailuser"}
: ${milter_manager_connection_spec=""}
: ${milter_manager_debug="NO"}

command=/usr/local/sbin/milter-manager
pidfile="${milter_manager_pid_file}"
command_args="--pid-file ${milter_manager_pid_file}"
if test -n "${milter_manager_user_name}"; then
    command_args="${command_args} --user-name ${milter_manager_user_name}"
fi
if test -n "${milter_manager_group_name}"; then
    command_args="${command_args} --group-name ${milter_manager_group_name}"
fi
if test -n "${milter_manager_socket_group_name}"; then
    command_args="${command_args} --unix-socket-group ${milter_manager_socket_group_name}"
fi
if test -n "${milter_manager_connection_spec}"; then
    command_args="${command_args} --spec ${milter_manager_connection_spec}"
fi
if test "$milter_manager_debug" = "YES"; then
    command_args="${command_args} --verbose"
else
    command_args="${command_args} --daemon"
fi

run_rc_command "$1"
今回意図した環境では、FreeBSD においては、デフォルト設定(自動検出)で問題なく動作しますが、実際には試行錯誤があったため、別記事でそのあたりを示していきます。

2022/12/27(火)メールサーバの中規模改修と基礎知識(5)~ OpenDKIM

OpenDKIM は、SPFと並んで、電子メール発信元の正当性を保証する技術のひとつです。
公式公開バージョンは、記事公開日時点では、2.10.3 です。一部のLinux ディストリビューションに 2.11.x が提供されているようですが、これは、RC版(Release Candidate:リリース候補の意)の位置づけの状態です。

OpenDKIM の概要

OpenDKIM は、電子メールメッセージを基に検証鍵を電子メールヘッダに埋め込み、DNSに登録した公開鍵を使って検証することで、電子メール発信元の正当性を確認し、電子メール改ざんがされていないことも確認する一助になります。

具体的には、予め送信元DNS情報に下記2つの TXTレコードを公開する形で登録しておき、
20221227_1_opendkim_2022.png

20221227_2_opendkim_2022.png


電子メール送信の際は、下記のように「DKIM-Signature:」というヘッダ行にて、一定のルールで計算された暗号キーを追加する処理を行います。
20221227_3_opendkim_2022.png

受信の際は、このDNS情報を基にメールヘッダに添付されてきたヘッダ暗号キーの検証を行い、この暗号キーに問題が無ければ「メール改ざんが無かった」と見做す、という処理を行うのが OpenDKIM の役割です。

送信の際の「DKIM-Signature:」ヘッダ行追加・受信の際の「DKIM-Signature:」ヘッダ行検証処理は、postfix にて、milter インタフェースを介して行うことになります。

OpenDKIM のインストール準備(1)

この先は、root アカウントにて、一連の作業を行います。
OpenDKIM は、公開鍵の管理にPython と Perlを使用しているため、この2つは必須です。
OpenDKIM をインストールする場合、FreeBSD13 では、下記のモジュールが事前に必要です。(但し、2022/12/24 現在)
これらの多くは、Ports や Package でインストールしても、管理上特段問題にはなりません。
 ・perl5-5.36.0 	(Ports から カテゴリ:lang)
 ・python39-3.9.16	(Ports から カテゴリ:lang)
 ・lua54-5.4.4 	(Ports から カテゴリ:lang)
 ・autoconf-2.71	(Ports から カテゴリ:devel)
 ・automake-1.16	(Ports から カテゴリ:devel)
 ・libltdl-2.4.7	(Ports から カテゴリ:devel)
 ・libtool-2.4.7	(Ports から カテゴリ:devel)
 ・gnutls-3.7.8_1	(Ports から カテゴリ:security)
 ・db5-5.3.28_9	(Ports から カテゴリ:databases)  ※ これは BerkeleyDB 5.3.28 です
OpenDKIM 2.10.3 は、OpenSSL 1.0 系までは対応しているものの、OpenSSL 1.1 系には対応していません。
昨今の FreeBSD にて採用されている OpenSSL は1.1 系のため、代替選択として、gnutls のインストールが必要になります。OpenSSL 1.1系対応なら、素直に OpenSSL を使うことで事が足りるのですが。。

更に、インストールに先立ち、OpenDKIM のセキュリティポリシーに従うため、ユーザ opendkim を、vipw や useradd コマンドで追加します:

vipw コマンドの場合は、下記の1行を編集画面で追加します。
opendkim::2006:3000::0:0:OpenDKIM execute user:/var/empty:/usr/sbin/nologin
vipw でユーザ追加した場合、/etc/group に下記の1行を例示のように変更しておきます:
mailuser:*:3000:opendkim
更に vipw でユーザ追加した場合は、つまらないセキュリティホールを作らないために、下記コマンドも一応実行しておきます:
# passwd opendkim

OpenDKIM のインストール準備(2)

先ず、ソースコードの展開を行います。
# cp opendkim-2.10.3.tar.gz /usr/local/src
# cd /usr/local/src
# tar xvzf opendkim-2.10.3.tar.gz
# cd opendkim-2.10.3
実は、実際にソースコードのコンパイルを実施したところ、FreeBSDにおいては、素のソースコードでは、GnuTLS 使用環境での構築も上手く出来ません。
具体的には、configure スクリプト、libopendkim/tests/Makefile.in、libopendkim/dkim-canon.c、libopendkim/dkim.c、miltertest/miltertest.c、opendkim/tests/Makefile.in、opendkim/opendkim-crypto.c、 opendkim/opendkim-lua.c の8本の変更が必要なようです。

これらは、patch コマンドを使用するか、手入力修正して、作業実施前にソースコードの修正を行う必要があります。
FreeBSD patch コマンド向けの差分ファイルは、https://ctrl.basekernel.ne.jp/rebase/download.html からダウンロードできるようにしてありますので、『opendkim-2.3.10用 FreeBSD 向け Patch ファイル群』をダウンロードしてみて、使えるようであれば、使ってみてください。

おそらく、以下のように実行すると良いと思います(当方では、この手法で出来ることの確認をしていません)
# cp opendkim-2.10.3.patch.gz /usr/local/src/opendkim-2.10.3
# cd /usr/local/src/opendkim-2.10.3
# tar xvzf opendkim-2.10.3.patch.gz
# patch < patch-configure.ac
# patch < patch-libopendkim__tests__Makefile.in
      ・
      ・
# patch < patch-opendkim_opendkim-lua.c

OpenDKIM のインストール

以下の手順で実行していきます:
# cd /usr/local/src/opendkim-2.10.3
# setenv CPPFLAGS '-I/usr/local/include -I/usr/include' 	  (configure が上手くライブラリを探せないため)
# setenv LDFLAGS '-L/usr/local/lib -L/usr/lib -L/usr/local/lib/db5'(configure が上手くライブラリを探せないため)
# unsetenv LD_LIBRARY_PATH	 

※以下、説明のために改行していますが、 ./configure の部分は、改行せずに、半角スペース区切りで、一気に入力。
# ./configure --localstatedir=/var 	(unix ソケットなどを格納するディレクトリ)
       --with-gnutls 		(openssl 1.1.x は未サポートのため、gnutls を使用する)
       --with-milter 		(milter インタフェースを構築する)
       --with-db 		(BerkeleyDBをサポートする)
       --with-domain 		(domain 設定をサポートする)
# make
# make install
ここまで、エラー無く出来たら、インストール不具合が1つあるので、下記の要領でファイル修正を行います。
# vi /usr/local/sbin/opendkim-genkey
1行目を下記の要領で変更
#!/usr/bin/perl
    ↓
#!/usr/local/bin/perl
次に設定作業を行います。
# mkdir /usr/local/etc/opendkim
# cd /usr/local/etc/opendkim
# mkdir example.com.keys 	(メールサーバ収容ドメイン毎にディレクトリを作成)
# cd example.com.keys
# /usr/local/sbin/opendkim-genkey --bits=2048 --domain=example.com --selector=sel
 	(--domain:収容ドメイン --selector:任意)
	※ --selector の文字列は公開鍵識別子になるので、適当にせずに何等かの規則を作るとよいでしょう。
	  pop1 とか mx1 とか、メールサーバの付番に対応すると良いと思います。
# cd ../
# chown -R opendkim:mailuser example.com.key
# vi KeyTable		※ 以下のように編集をする(複数ドメインになる場合は、行追加)
sel._example.com example.com:sel:/usr/local/etc/opendkim/example.com.keys/sel.private

# vi SigningTable	※ 以下のように編集をする(複数ドメインになる場合は、行追加)
*@example.com sel._domainkey.example.com

# chown opendkim:mailuser KeyTable
# chown opendkim:mailuser SigningTable
ここで、KeyTable ファイルへの記述方法ですが、
①._②:①:/usr/local/etc/opendkim/②.keys/①.private
(①= --selector への設定文字列 ②= --domain への設定文字列)

SigningTable ファイルへの記述方法は、
*@② ①._domainkey.②
(①= --selector への設定文字列 ②= --domain への設定文字列) のようにします。

次に、上記で /usr/local/sbin/opendkim-genkey を実行した時に生成された、sel.txt の内容と、取り扱いポリシーをDNSへ登録します。(--selector の指定により、拡張子 .txt は変わりませんが、ファイル名は変わります。)

生成された、sel.txt の内容を見ると、テキスト形式にて、
sel._domainkey  IN      TXT     ( "v=DKIM1; k=rsa; "         "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAulH2mMsm8eP7bpAmlXI9EE5TQ8tjWBaMDaSP34zB+0+LLjJhbi9dhGV9T5reppaxaU7lzokGaHJX+l9X4sA53G9PlpzQ0JwsGO5Z1E9LaN1CDijqtGNQWr7bdLpy4AwzfhDThY25rwzMg4Wt4LK74Ba7Q5a2M9m0d/gEqs6ZWrK/6DiQ1qnzU7JTm13SZRBvnNhmLBMnSu1sz3"
          "Hc8nE+02oAJC04xCjI8XUowu+sxyJQm7OY97MOvOt9HAXt/jbBVyG1scJTggjYsH1JcfC1xbE1VnNiWSkpid2P+4bXy/GIG90nZ7We4KNMshXEby/FbUZPPERHW1ntCyga58rPJQIDAQAB" )  ;
----- DKIM key sel0 for example.jp
のような内容になっています。この例に沿うと、example.jp のDNSゾーンに、
 ・name : sel._domainkey.example.jp.
 ・type : TXT
 ・content:"v=DKIM1; .... 以下 .... yga58rPJQIDAQAB" まで
を登録します。もう一つ、example.jp のDNSゾーンに
 ・name : _adsp._domainkey.example.jp.
 ・type : TXT
 ・content:"dkim=unknown"
を登録します。このパラメータの意味は下記のとおりです:
unknown該当ドメインからの送信メールには、メール作成者署名(DKIM)がある場合と無い場合がある
all該当ドメインからの送信メールには、必ずメール作成者署名(DKIM)がある
もしメール作成者署名(DKIM)が得られない場合の処置は受信側の任意。(通常は配送する)
discardable該当ドメインからの送信メールには、必ずメール作成者署名(DKIM)がある。
もしメール作成者署名(DKIM)が得られない場合は、受信者はそのメールを破棄することが望まれる
日本国内の電子メール環境を鑑みた場合、ここは「unknown」が無難と思います。

起動・停止スクリプトの設置(FreeBSD に特化している項目)

このスクリプトは一度作っておくと、バージョンアップの際に再作成の必要はありません。

○ OpenDKIM の起動スクリプト
/usr/local/etc/rc.d/ ディレクトリ配下に、milter-opendkim のファイル名で下記内容を作成します。
実行権限を与えることを忘れないようにしてください:
#!/bin/sh

# PROVIDE: milter-opendkim
# REQUIRE: DAEMON
# BEFORE: mail
# KEYWORD: shutdown

# Define these milteropendkim_* variables in one of these files:
#       /etc/rc.conf
#       /etc/rc.conf.local
#       /etc/rc.conf.d/milteropendkim
#
# milteropendkim_enable (bool):   Set to "NO" by default.
#                             Set it to "YES" to enable dkim-milter
# milteropendkim_uid (str):       Set username to run milter.
# milteropendkim_gid (str):       Set group to run milter.
# milteropendkim_profiles (list): Set to "" by default.
#                             Define your profiles here.
# milteropendkim_cfgfile (str):   Configuration file. See opendkim.conf(5)
#
# milteropendkim_${profile}_* :   Variables per profile.
#                             Sockets must be different from each other.
#
# milteropendkim_socket_perms (str):
#                                 Permissions for local|unix socket.
#
#  all parameters below now can be set in opendkim.conf(5).
# milteropendkim_socket (str):    Path to the milter socket.
# milteropendkim_domain (str):    Domainpart of From: in mails to sign.
# milteropendkim_key (str):       Path to the private key file to sign with.
# milteropendkim_selector (str):  Selector to use when signing
# milteropendkim_alg (str):       Algorithm to use when signing
# milteropendkim_flags (str):     Flags passed to start command.

. /etc/rc.subr

name="milteropendkim"
rcvar=milteropendkim_enable

extra_commands="reload"
start_precmd="dkim_prepcmd"
start_postcmd="dkim_start_postcmd"
stop_postcmd="dkim_postcmd"
command="/usr/local/sbin/opendkim"
_piddir="/var/run/milteropendkim"
pidfile="${_piddir}/pid"
sig_reload="USR1"

load_rc_config $name

#
# DO NOT CHANGE THESE DEFAULT VALUES HERE
#
: ${milteropendkim_enable:="NO"}
: ${milteropendkim_uid:="opendkim"}
: ${milteropendkim_gid:="mailuser"}
: ${milteropendkim_cfgfile:="/usr/local/etc/opendkim/opendkim.conf"}
: ${milteropendkim_socket_perms:="0775"}

# Options other than above can be set with $milteropendkim_flags.
# see dkim-milter documentation for detail.

extra_commands="reload"
start_precmd="dkim_prepcmd"
start_postcmd="dkim_start_postcmd"
stop_postcmd="dkim_cleansockets"
command="/usr/local/sbin/opendkim"
sig_reload="USR1"

dkim_cleansockets()
{
    case ${milteropendkim_socket%:*} in
    local|unix)
        rm -f "${milteropendkim_socket#*:}"
        ;;
    esac
}

dkim_get_pidfile()
{
        if get_pidfile_from_conf PidFile ${milteropendkim_cfgfile#-x }; then
                pidfile="$_pidfile_from_conf"
        else
                pidfile="/var/run/milteropendkim/${profile:-pid}"
        fi
}

dkim_prepcmd()
{
    dkim_cleansockets
    dkim_get_pidfile
    if [ ! -d "$(dirname "$pidfile")" ]; then
        mkdir "$(dirname "$pidfile")"
    fi
    case ${milteropendkim_socket%:*} in
    local|unix)
        socketfile=${milteropendkim_socket#*:}
        install -d -o ${milteropendkim_uid%:*} -g $milteropendkim_gid \
            -m ${milteropendkim_socket_perms} \
               ${pidfile%/*} ${socketfile%/*}
        ;;
    esac
}

dkim_start_postcmd()
{
    case ${milteropendkim_socket%:*} in
    local|unix)
        # postcmd is executed too fast and socket is not created before checking...
        sleep 1
        chmod -f ${milteropendkim_socket_perms} ${milteropendkim_socket#*:}
        ;;
    esac
}

if [ -n "$2" ]; then
    profile="$2"
    if [ -n "${milteropendkim_profiles}" ]; then
        pidfile="${_piddir}/${profile}.pid"
        eval milteropendkim_enable="\${milteropendkim_${profile}_enable:-${milteropendkim_enable}}"
        eval milteropendkim_socket="\${milteropendkim_${profile}_socket:-}"
        eval milteropendkim_socket_perms="\${milteropendkim_${profile}_socket_perms:-}"
        if [ -z "${milteropendkim_socket}" ];then
            echo "You must define a socket (milteropendkim_${profile}_socket)"
            exit 1
        fi
        eval milteropendkim_cfgfile="\${milteropendkim_${profile}_cfgfile:-${milteropendkim_cfgfile}}"
        eval milteropendkim_domain="\${milteropendkim_${profile}_domain:-${milteropendkim_domain}}"
        eval milteropendkim_key="\${milteropendkim_${profile}_key:-${milteropendkim_key}}"
        eval milteropendkim_selector="\${milteropendkim_${profile}_selector:-${milteropendkim_selector}}"
        eval milteropendkim_alg="\${milteropendkim_${profile}_alg:-${milteropendkim_alg}}"
        eval milteropendkim_flags="\${milteropendkim_${profile}_flags:-${milteropendkim_flags}}"
        if [ -f "${milteropendkim_cfgfile}" ];then
            milteropendkim_cfgfile="-x ${milteropendkim_cfgfile}"
        else
            milteropendkim_cfgfile=""
        fi
        if [ -n "${milteropendkim_socket}" ];then
            _socket_prefix="-p"
        fi
        if [ -n "${milteropendkim_uid}" ];then
            _uid_prefix="-u"
            if [ -n "${milteropendkim_gid}" ];then
                milteropendkim_uid=${milteropendkim_uid}:${milteropendkim_gid}
            fi
        fi
        if [ -n "${milteropendkim_domain}" ];then
            milteropendkim_domain="-d ${milteropendkim_domain}"
        fi
        if [ -n "${milteropendkim_key}" ];then
            milteropendkim_key="-k ${milteropendkim_key}"
        fi
        if [ -n "${milteropendkim_selector}" ];then
            milteropendkim_selector="-s ${milteropendkim_selector}"
        fi
        if [ -n "${milteropendkim_alg}" ];then
            milteropendkim_alg="-S ${milteropendkim_alg}"
        fi
        dkim_get_pidfile
        command_args="-l ${_socket_prefix} ${milteropendkim_socket} ${_uid_prefix} ${milteropendkim_uid} -P ${pidfile} ${milteropendkim_cfgfile} 
${milteropendkim_domain} ${milteropendkim_key} ${milteropendkim_selector} ${milteropendkim_alg}"
    else
        echo "$0: extra argument ignored"
    fi
else
    if [ -n "${milteropendkim_profiles}" ] && [ -n "$1" ]; then
        if [ "$1" != "restart" ]; then
            for profile in ${milteropendkim_profiles}; do
                echo "===> milteropendkim profile: ${profile}"
                /usr/local/etc/rc.d/milter-opendkim $1 ${profile}
                retcode="$?"
                if [ "${retcode}" -ne 0 ]; then
                    failed="${profile} (${retcode}) ${failed:-}"
                else
                    success="${profile} ${success:-}"
                fi
            done
            exit 0
        else
            restart_precmd=""
        fi
    else
        if [ -f "${milteropendkim_cfgfile}" ];then
            milteropendkim_cfgfile="-x ${milteropendkim_cfgfile}"
        else
            milteropendkim_cfgfile=""
        fi
        if [ -n "${milteropendkim_socket}" ];then
            _socket_prefix="-p"
        fi
        if [ -n "${milteropendkim_uid}" ];then
            _uid_prefix="-u"
            if [ -n "${milteropendkim_gid}" ];then
                milteropendkim_uid=${milteropendkim_uid}:${milteropendkim_gid}
            fi
        fi
        if [ -n "${milteropendkim_domain}" ];then
            milteropendkim_domain="-d ${milteropendkim_domain}"
        fi
        if [ -n "${milteropendkim_key}" ];then
            milteropendkim_key="-k ${milteropendkim_key}"
        fi
        if [ -n "${milteropendkim_selector}" ];then
            milteropendkim_selector="-s ${milteropendkim_selector}"
        fi
        if [ -n "${milteropendkim_alg}" ];then
            milteropendkim_alg="-S ${milteropendkim_alg}"
        fi
        dkim_get_pidfile
        command_args="-l ${_socket_prefix} ${milteropendkim_socket} ${_uid_prefix} ${milteropendkim_uid} -P ${pidfile} ${milteropendkim_cfgfile} 
${milteropendkim_domain} ${milteropendkim_key} ${milteropendkim_selector} ${milteropendkim_alg}"
    fi
fi
run_rc_command "$1"
OpenDKIM の設定は、これで終わりです。

2022/12/26(月)メールサーバの中規模改修と基礎知識(4)~ clamAV

clamav_logo.png

clamAV は、サーバサイドでの使用を想定したコンピュータウィルス検出ソフトウェアです。
2022/11/23 に最新版 Ver 1.0.0 がリリースされており、Ver 0.103.7 が旧版の安定バージョンとして 2022/07/26 にリリースされています。

ClamAV の概要

送信・受信共に電子メールメッセージを検査させるため、postfix で、milter インタフェースを介した動作とします。
milter は、インターネット黎明期から Unix 系で使用されてきた Sendmail(電子メール交換サーバソフトウェア) で「拡張機能」として初めて出現し、「Sendmail Content Management API」の短縮名称とされています。

つまり、電子メール送受信に際して、さまざまな拡張機能を提供する仕組みなのですが、「これ」といった仕様書がなく、過去に開発されたインタフェース手順を、ソースコードレベルで継承・踏襲しているといった感じのようです。

この「付加機能提供インタフェース」である、milter を利用して、電子メールメッセージにコンピュータウィルスが含まれているか否かを検査し、結果を反映するのが clamAV の役割です。
混同されがちなものに「コンピュータウィルス」と「spam」があります。前者は、使用しているパソコンや情報機器に具体的な障害を与えるものを含むもの(具体的には添付ファイルや障害を与えるコンテンツへのリンク)、「spam」は嫌がらせ目的や不快感を繰り返し助長するメッセージを主に指します。

なので、コンピュータウィルスの検査で、spam を実用的に検出するのは難しいものがあり、ClamAV と SpamAssassin を併用するのは、そういった観点があります。

ClamAV のインストール

インストール作業は、root アカウントで行います。
clamAV をインストールする場合、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)
 ・cmake-core-3.24.3_2	 (Ports から カテゴリ:devel)
 ・libxml2-2.10.3_1	 (Ports から カテゴリ:textproc)
 ・curl-7.86.0		 (Ports から カテゴリ:ftp)
 ・json-c-0.16		 (Ports から カテゴリ:devel)
 ・jsoncpp-1.9.5	 (Ports から カテゴリ:devel)
 ・check-0.15.2	 (Ports から カテゴリ:devel)
 ・libmspack-0.10.1	 (Ports から カテゴリ:archivers)
 ・tomsfastmath-0.13.1_4(Ports から カテゴリ:math)
 ・python39-3.9.16	 (Ports から カテゴリ:lang)
 ・py39-pytest-7.2.0,1	 (Ports から カテゴリ:devel)
 ・rust-1.66.0		 (Ports から カテゴリ:lang)
更に、インストールに先立ち、clamAV のセキュリティポリシーに従うため、ユーザ clamav を、vipw や useradd コマンドで追加します:

vipw コマンドの場合は、下記の1行を編集画面で追加します。
clamav::65002:65002::0:0:Clam antivirus:nonexistent:/usr/sbin/nologin
vipw でユーザ追加した場合、/etc/group に下記の1行を追加しておきます:
clamav:*:65002:
更に vipw でユーザ追加した場合は、つまらないセキュリティホールを作らないために、下記コマンドも一応実行しておきます:
# passwd clamav
clamAV のコンパイルは、Ver 0.105.0 から、cmake を使うようになりました。下記のように順に実施していきます:
# cp clamav-1.0.0.tar.gz /usr/local/src	 
# cd /usr/local/src	 
# tar xvzf clamav-1.0.0.tar.gz	 
# cd clamav-1.0.0	 
# mkdir build	 
# cd build	 
# cmake .. -D ENABLE_JSON_SHARED=OFF 	(JSONライブラリはスタティックリンク)
Configuration に成功すると、下記のようなサマリーが出力されます。
何かしらの問題がある場合は、問題点を指摘するメッセージが表示され、Configuration プロセスは失敗します。
-- Configuration Options Summary --
    Package Version:        ClamAV 1.0.0
    libclamav version:      11:0:0
    libfreshclam version:   2:2:0
    Install prefix:         /usr/local
    Install database dir:   /usr/local/share/clamav
    Install config dir:     /usr/local/etc
    Host system:            FreeBSD-13.1-RELEASE-p5
    Target system:          FreeBSD-13.1-RELEASE-p5
    Compiler:
        Build type:         RelWithDebInfo
        C compiler:         /usr/bin/cc
        C++ compiler:       /usr/bin/c++
        Rust toolchain:     /usr/local/bin/cargo (1.66.0)
        CFLAGS:             -O2 -g
        CXXFLAGS:           -O2 -g
        WARNCFLAGS:          -Wall -Wextra -Wformat-security
    Build Options:
        Build apps:         ON
        Shared library:     ON
        Static library:     OFF
        Enable UnRAR:       ON
        Examples:           OFF
        Tests:              ON
        Build man pages:    ON
        Build doxygen HTML: OFF
        Maintainer Mode:    OFF
    Build Extras:
        Build milter:       ON  (toggle with -DENABLE_MILTER=ON/OFF)
-- Engine Options --
        Bytecode Runtime:
            interpreter
-- Test Dependencies --
        Unit Test Framework:
            libcheck        /usr/local/include
                            /usr/local/lib/libcheck.so
        Feature Test Framework:
            python3         /usr/local/bin/python3.9
            test command    pytest;-v
-- libclamav Dependencies --
        Compression support:
            bzip2           /usr/include
                            /usr/lib/libbz2.so
            zlib            /usr/include
                            /usr/lib/libz.so
        XML support:
            libxml2         /usr/local/include/libxml2;/usr/local/include/libxml2
                            /usr/local/lib/libxml2.so
        RegEx support:
            libpcre2        /usr/local/include
                            /usr/local/lib/libpcre2-8.so
        Crypto support:
            openssl         /usr/include
                            /usr/lib/libssl.so;/usr/lib/libcrypto.so
        JSON support:
            json-c          /usr/local/include/json-c
                            /usr/local/lib/libjson-c.a
        Threading support:
            pthread
        Locale support:
            iconv           /usr/local/include
                            /usr/local/lib/libiconv.so
-- libfreshclam Extra Dependencies --
        HTTP support:
            curl            /usr/local/include
                            /usr/local/lib/libcurl.so
-- Application Extra Dependencies --
        GUI support:
            ncurses         /usr/include
                            /usr/lib/libncurses.so
        Milter Support:
            libmilter       /usr/include
                            /usr/lib/libmilter.so

-- Configuring done
-- Generating done
-- Build files have been written to: /usr/local/src/clamav-1.0.0/build
引き続き、以下の手順でソースコードからの構築を実施します:
# cmake --build . 			(ウィルススキャナの構築)
# cmake --build . --target install 	(ウィルススキャナのインストール)
続いて、動作に必要な環境とログファイルの初期化です。
# mkdir /var/run/clamav 		(clamd のプロセス情報保存領域)
# chown clamav:mailuser /var/run/clamav 	 
 	 
# touch /var/log/clamd.log	 
# chown clamav /var/log/clamd.log	 
# touch /var/log/clamav-milter.log	 
# chown clamav /var/log/clamav-milter.log	 
# touch /var/log/freshclam.log	 
# chown clamav /var/log/freshclam.log	 
# mkdir /usr/local/share/clamav		(ウィルスデータベース格納先)
# chown clamav:mailuser /usr/local/share/clamav
clamd,freshclam,clamAV-milter それぞれ、別々に動作します。
それぞれの設定ファイルを、/usr/local/etc 配下に作成します。通常、ここまでの手順を踏んでいれば、インストールされているはずです。

先ず、/usr/local/etc/clamd.conf の設定ファイルを、下記を参考に該当行を変更します。
# Example				  ← 削除
LogFile /var/log/clamd.log		  ← 追加又は変更
LogTime yes				  ← 追加又は変更
PidFile /var/run/clamav/clamd.pid	  ← 追加又は変更
DatabaseDirectory /usr/local/share/clamav  ← 追加又は変更
LocalSocket /var/run/clamav/clamd	  ← 追加又は変更
FixStaleSocket yes			  ← 追加又は変更
User clamav				  ← 追加又は変更
AllowSupplementaryGroups yes		  ← 追加又は変更
続いて、/usr/local/etc/freshclam.conf の設定ファイルを、下記を参考に該当行を変更します。
# Example				  ← 削除
DatabaseDirectory /usr/local/share/clamav  ← 追加又は変更
UpdateLogFile /var/log/freshclam.log	  ← 追加又は変更
PidFile /var/run/clamav/freshclam.pid	  ← 追加又は変更
DatabaseOwner clamav			  ← 追加又は変更
DatabaseMirror db.jp.clamav.net		  ← 追加又は変更
DatabaseMirror clamav.nara.wide.ad.jp	  ← 追加又は変更
NotifyClamd /usr/local/etc/clamd.conf	  ← 追加又は変更
最後に、/usr/local/etc/clamav-milter.confの設定ファイルを、下記を参考に該当行を変更します。
# Example					 ← 削除
MilterSocket unix:/var/run/clamav/clmilter.sock	 ← 追加又は変更
User clamav					 ← 追加又は変更
AllowSupplementaryGroups yes			 ← 追加又は変更
PidFile /var/run/clamav/clamav-milter.pid	 ← 追加又は変更
TemporaryDirectory /var/tmp			 ← 追加又は変更
ClamdSocket unix:/var/run/clamav/clamd		 ← 追加又は変更
LogFile /var/log/clamav-milter.log		 ← 追加又は変更
3つの設定ファイル変更が終わったら、以下の手順でウィルスパターン定義を初期化・最新版に更新します:
# rehash	 
# freshclam 	(ウィルススキャンデータベースを最新の状態にする)

起動・停止スクリプトの設置(FreeBSD に特化している項目)

これらのスクリプトは一度作っておくと、バージョンアップの際に再作成の必要はありません。

○ clamd の起動スクリプト
/usr/local/etc/rc.d/ ディレクトリ配下に、clamav_clamd のファイル名で下記内容を作成します。
実行権限を与えることを忘れないようにしてください:
#!/bin/sh

# PROVIDE: clamav_clamd
# REQUIRE: LOGIN
# BEFORE: mail
# KEYWORD: shutdown

#
# Add the following lines to /etc/rc.conf to enable clamd:
#
# clamav_clamd_enable="YES"
# clamav_clamd_flags=""
#
# See clamd(8) for flags
#

. /etc/rc.subr

name=clamav_clamd
rcvar=clamav_clamd_enable

# read settings, set default values
load_rc_config "$name"

: ${clamav_clamd_enable:=NO}
: ${clamav_clamd_socket="/var/run/clamav/clamd"}
: ${clamav_clamd_pidfile="/var/run/clamav/clamd.pid"}
: ${clamav_clamd_user="clamav"}

command=/usr/local/sbin/clamd
required_dirs=/var/db/clamav
required_files=/usr/local/etc/clamd.conf

start_precmd=clamav_clamd_precmd
extra_commands=reload
reload_cmd="/usr/local/bin/clamdscan --reload"

clamav_clamd_precmd()
{
        local rundir=${clamav_clamd_pidfile%/*}
        if [ ! -d $rundir ] ; then
                install -d -m 0755 -o ${clamav_clamd_user} -g ${clamav_clamd_user} $rundir
        fi
        if [ ! -f /var/db/clamav/main.cvd -a ! -f /var/db/clamav/main.cld ];then
                echo "Missing /var/db/clamav/*.cvd or *.cld files.  You must run freshclam first"
                exit 1
        fi
}

run_rc_command "$1"
○ freshclam の起動スクリプト
これは、一定時間毎に新しいコンピュータウィルス定義パターンを更新するためのものです。
/usr/local/etc/rc.d/ ディレクトリ配下に、clamav_freshclam のファイル名で下記内容を作成します。
実行権限を与えることを忘れないようにしてください:
#!/bin/sh

# PROVIDE: clamav_freshclam
# REQUIRE: LOGIN clamav_clamd
# BEFORE: mail
# KEYWORD: shutdown

#
# Add the following lines to /etc/rc.conf to enable the freshclam daemon:
#
# clamav_freshclam_enable="YES"
# clamav_freshclam_flags=""
#
# See freshclam(1) for flags
#

. /etc/rc.subr

name=clamav_freshclam
rcvar=clamav_freshclam_enable

# read settings, set default values
load_rc_config ${name}

: ${clamav_freshclam_enable:=NO}
: ${clamav_freshclam_pidfile=/var/run/clamav/freshclam.pid}
: ${clamav_freshclam_user=clamav}

command=/usr/local/bin/freshclam
pidfile=${clamav_freshclam_pidfile}
command_args="--daemon -p ${pidfile}"
required_dirs=/var/db/clamav
required_files=/usr/local/etc/freshclam.conf

start_precmd=clamav_freshclam_precmd

clamav_freshclam_precmd()
{
        local rundir=${clamav_freshclam_pidfile%/*}
        if [ ! -d $rundir ] ; then
                install -d -m 0755 -o ${clamav_freshclam_user} -g ${clamav_freshclam_user} $rundir
        fi
}

run_rc_command "$1"
○ clamav-milter の起動スクリプト
/usr/local/etc/rc.d/ ディレクトリ配下に、clamav_milter のファイル名で下記内容を作成します。
実行権限を与えることを忘れないようにしてください:
#!/bin/sh

# PROVIDE: clamav_milter
# REQUIRE: LOGIN clamav_clamd
# BEFORE: mail
# KEYWORD: shutdown

#
# Add the following lines to /etc/rc.conf to enable clamav-milter:
#
# clamav_milter_enable="YES"
#
# See clamav-milter(1) for flags
#

. /etc/rc.subr

name=clamav_milter
rcvar=clamav_milter_enable

load_rc_config $name

: ${clamav_milter_enable:=NO}
: ${clamav_milter_socket="/var/run/clamav/clmilter.sock"}
: ${clamav_milter_conf="/usr/local/etc/clamav-milter.conf"}
: ${clamav_milter_flags="-c ${clamav_milter_conf}"}
: ${clamav_milter_socktimeout=60}
: ${clamav_milter_socket_mode=777}
: ${clamav_milter_socket_user=clamav}
: ${clamav_milter_socket_group=mailuser}
: ${clamav_clamd_enable:=NO}
: ${clamav_clamd_socket="/var/run/clamav/clamd"}

command=/usr/local/sbin/clamav-milter
required_dirs=/var/db/clamav
required_files=${clamav_milter_conf}

start_precmd=start_precmd
start_postcmd=start_postcmd

start_precmd()
{
        if [ -S "$clamav_milter_socket" ]; then
                warn "Stale socket $clamav_milter_socket removed."
                rm "$clamav_milter_socket"
        fi
        rc_flags="${flags:-$clamav_milter_flags}"

        clamav_clamd_socket_prefix=${clamav_clamd_socket%:*}
        # We can have inet or inet6, try to remove 6
        clamav_clamd_socket_prefix=${clamav_clamd_socket_prefix%6}

        if checkyesno clamav_clamd_enable && [ "x$clamav_clamd_socket" != "x" -a "${clamav_clamd_socket_prefix}" != "inet" ]; then
                echo -n "Waiting for clamd socket.. "
                i=${clamav_milter_socktimeout}
                while [ $i -ne 0 ]
                do
                        [ -S "$clamav_clamd_socket" ] && break
                        if [ `expr $i % 10` -eq 0 ]; then
                                echo -n "${i}.. "
                        fi
                        sleep 1
                        i=$(($i-1))
                done
                echo
                if [ $i -eq 0 ]; then
                        echo "There is no clamd socket (${clamav_clamd_socket})!"
                        exit 1
                fi
        fi
}

start_postcmd()
{
        clamav_milter_socket_prefix=${clamav_milter_socket%:*}
        # We can have inet or inet6, try to remove 6
        clamav_milter_socket_prefix=${clamav_milter_socket_prefix%6}

        if [ "x$clamav_milter_socket" != "x" -a "${clamav_milter_socket_prefix}" != "inet" ]; then
                echo -n "Waiting for clamav-milter socket.. "
                i=${clamav_milter_socktimeout}
                while [ $i -ne 0 ]
                do
                        [ -S "$clamav_milter_socket" ] && break
                        if [ `expr $i % 10` -eq 0 ]; then
                                echo -n "${i}.. "
                        fi
                        sleep 1
                        i=$(($i-1))
                done
                echo
                if [ $i -eq 0 ]; then
                        echo "There is no clamav-milter socket (${clamav_milter_socket})!"
                        exit 1
                fi
                /bin/chmod ${clamav_milter_socket_mode} ${clamav_milter_socket}
                /usr/sbin/chown ${clamav_milter_socket_user}:${clamav_milter_socket_group} ${clamav_milter_socket}
        fi
}

run_rc_command "$1"
clamAV のインストールは、これでとりあえず完了です。

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日未満の証明書のみを更新します。

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

2022/12/24(土)メールサーバの中規模改修と基礎知識(2)~ SpamAssassin

SpamAssassin は、定番となっている spam 検出ソフトウェアで、サーバサイドでもクライアントサイドでも使用できる汎用性があります。2022/12/14(アナウンスは 2022/12/17) に Ver 4.0.0 がリリースされました。

具体的には、電子メールヘッダを含めた電子メールメッセージ全体を与え、各種のテストを行い、その結果を spam 度合値として、判定値を提示するような動作になります。

概念図(電子メールメッセージは、① → ② のルートで流れる)

                         ①         ①
spamc《チェック対象の電子メールメッセージ全体》===> socket通信 ===> spamd(SpamAssassin 本体)
   《チェック結果を含む電子メールメッセージ》<=== socket通信 <===
                          ②         ②
上図のように、サーバサイドでは、spamd をデーモン形式で稼働させておき、spamc というクライアントプログラムにて、受信した電子メールメッセージ全体を、spamd に垂れ流し、メッセージの電子メールヘッダ先頭にその判定値が付加されたものが与えたメッセージ全体と共に出力される挙動になります。

spamc から見ると、標準入力(stdin) に電子メールメッセージを流し込み、標準出力(stdout)に結果が出てくるような挙動になります。

SpamAssassinは、下図のように X-Spam-Checker-Version・X-Spam-Level・X-Spam-Status という3つのメールヘッダを処理結果として付加します。
20221224_mailserver_2022enhance.png

このうち、X-Spam-Status というメールヘッダ行が重要で、例示では、「score=-0.6」が spam 度合判定値、「required=4.0」が、spam と見做すしきい値で、この値は別途個別設定出来ます。
電子メール配送の最終段階で、このメールヘッダ部分を参照して、spam メール隔離か通常配送かを決定し、処理する仕組みになります。
「autolearn=ham」というのは、『spam ではないと自動的にパターン学習した』という意味で、spam として見做されると、ここは「autolearn=spam」という表示になります。
「autolearn_force=no」は、SpamAssassin の自動学習機能の如何に関わらず、強制的に「spam である」と学習させる判定値で、この例では「required=4.0」以上の場合に強制的に SpamAssassin に spam メールパターンの学習をさせる挙動になります。

インストール

インストール作業は、必ず root アカウントで行います。
また、SpamAssassin は、Perl 上で動作するため、事前に Perl 5.26 以降が必要です。
FreeBSD,NetBSD 他のUnix系OS、Linux系OSにおいては、Perl をパッケージでインストールしても問題はないです。
お勧めは、現時点(2022/12/24 現在)で最新バージョンの Perl 5.36。

FreeBSD だと、Ports で以下の手順でインストールするのが確実。(依存パッケージが先に自動的にインストールされる)
# cd /usr/ports/lang/perl5.36
# make install
# make clean

また、インストールに2つの方法がありますが、現状では後者の方法(方法その2)しかまともに出来ないみたいです。(当方の環境にて)

○ 方法その1(動作に必要な依存モジュールは自動的に探してインストールされる):
# perl -MCPAN -e shell
cpan> install Mail::SpamAssassin
この方法だと、test 段階で、'spamd が見つからない' とメッセージが出て test自体がなかなか終わらない。
3.4系ではこの問題は出ません。なので、次に示す方法が有効:

○ 方法その2
・まず SpamAssassin の公式ダウンロードページ(https://spamassassin.apache.org/downloads.cgi)から、
 Mail-SpamAssassin-4.0.0.tar.gz または、Mail-SpamAssassin-4.0.0.tar.bz2 をダウンロードし、インストール機器上にアップロードする。
・この方法の場合、CPAN で予め、下記モジュールをインストールしておいた方がよい:
# perl -MCPAN -e shell	 
 cpan> install NetAddr::IP	 
 cpan> install HTML::Parser	 
 cpan> install Digest::SHA1	 
 cpan> install IP::Country	 
 cpan> install IP::Country::DB_File	 
 cpan> install Net::Ident	 
 cpan> install IO::Socket::SSL	 
 cpan> install LWP::UserAgent	 
 cpan> install BSD::Resource	 
 cpan> install Mail::SPF	 
 cpan> install Mail::DKIM	 
 cpan> exit
・次に、下記コマンドを順に実行する。
# cp Mail-SpamAssassin-4.0.0.bz2 /usr/locall/src
# cd /usr/local/src
# tar xvzf Mail-SpamAssassin-4.0.0.bz2
# cd Mail-SpamAssassin-4.0.0
# perl Makefile.PL
# make
# make install 
インストール完了後、方法その1・方法その2の何れであっても、必ず以下のコマンドを実行しておきます。
# rehash
# sa-update --no-gpg
また、運用時は、SpamAssassin 自体が意図しているセキュリティポリシーに合わせるため、ここで専用のユーザを予めvipw や useradd コマンドで作っておきます。
vipw の場合は、編集画面で、下記の行を追加しておきます:
spamd::783:783::0:0:SpamAssassin Daemon:/nonexistent:/usr/sbin/nologin
vipw でユーザ追加した場合は、/etc/group ファイルに下記の行を追加しておきます:
spamd:*:783:
ユーザID、グループIDは、783 にこだわる必要はありませんが、当然のことながら、ユーザID・グループIDが他と重複しないように注意です。
更に vipw でユーザ追加した場合は、つまらないセキュリティホールを作らないために、下記コマンドも一応実行しておきます:
# passwd spamd
vipw,passwd,useradd コマンドは、どれも必ずroot ユーザ上で行います。

起動・停止スクリプトの設置(FreeBSD に特化している項目)

このスクリプトは一度作っておくと、バージョンアップの際に再作成の必要はありません。

○ spamd の起動スクリプト
/usr/local/etc/rc.d ディレクトリ配下に、sa-spamd のファイル名で下記内容を作成します。
1行目から6行目( KEYWORD までの行 ) は、一見するとコメント行そのものですが、意味を持っているため削除しないようにしてください。/usr/local/etc/rc.d ディレクトリ配下のスクリプトは、全てこの挙動になります。

また、実行権限を与えることを忘れないようにしてください:
#!/bin/sh

# PROVIDE: spamd
# REQUIRE: LOGIN
# BEFORE: mail
# KEYWORD: shutdown

#
# Add the following line to /etc/rc.conf to enable spamd:
#
#   spamd_enable="YES"
#
# You can pass flags to spamd with spamd_flags="..."
# To change the user that spamd runs as, use
#
#    spamd_flags="-u USER [-H /path/to/home... we suggest /var/spool/spamd]"
#
# To keep your user-config in a SQL database, use
#
#        spamd_flags="-Q"
#
# and remove -c (auto-create user preference files).
#

. /etc/rc.subr

name=spamd
rcvar=${name}_enable

extra_commands="reload"
load_rc_config $name

start_precmd="precmd"
restart_precmd="precmd"
stop_cmd="spamd_stop"
pidfile=${spamd_pidfile:-"/var/run/${name}/${name}.pid"}

# Set defaults
: ${spamd_enable:="NO"}
: ${spamd_flags:="-c -u spamd -r ${pidfile} -x --virtual-config-dir=/var/mail/%d/%l/spamassassin/spamd"}

command=/usr/local/bin/${name}
command_args="-d -r ${pidfile}"
command_interpreter="/usr/local/bin/perl"
required_dirs="/usr/local/share/spamassassin"

precmd()
{
        if [ ! -d /var/run/${name} ]; then
                mkdir -p /var/run/${name}
                chown spamd:spamd /var/run/${name}
        fi
}

spamd_stop()
{
  kill -INT `cat /var/run/spamd/spamd.pid`
  echo ' Stop sa-spamd 4.0.0 '
}
run_rc_command "$1"
あとは、簡単な設定作業が残っていますが、メールサーバ構築作業全体の最後のほうで行います。
なので、とりあえずこのフェーズは完了です。

2022/12/23(金)メールサーバの中規模改修と基礎知識(1)

背景や前提条件など

このシリーズは、8回に分けて記事を起こした時点における、今どきのメールサーバ運営管理上のインストール手順や参考情報を掲載します。Portsやバイナリパッケージに頼らず、ソースコードから自力で構築する手順から紹介しています。
『時代錯誤だ!』と嘲笑されようが、これを経験することが基礎知識を維持し、育むにはとても重要なのです。

OSは FreeBSD13 ですが、他のLinux 系OSにおいても参考になる部分が多いと思います。
また、ここでは OpenLDAP のインストールや設定を終えており、LDAP 検索にて、電子メールアドレスまたはSASLユーザ名を検索条件として与えると、
 ・SASL パスワード
 ・UID
 ・GID
 ・homeDirectory
 ・status〔有効にしている(valid)か、無効にしている(invalid)か〕
の情報が得られる仕組みが出来ていることを前提にしています。

実は、約2年前に計画していた改修ですが、
別の企業にて壊れたオンラインシステムの復旧作業・改修作業を事実上一手に引き受けた状態になり、
そちらを優先して、自社システムの細かな不具合がないがしろになっていた状態でした。

当初技術的に先行していた(と自負できる)メールサーバの構成が15年以上経過して色あせてきたことと、
昨今のOutlookやアンチウィルスソフトとの相性が悪くなっているため、中規模改修を行ってこの細かな不具合を出来るだけ解消しようと、やっとのことで実施に移しています。

一口に『メールサーバ』と言っても、知っている人は知っているのですが、構成は複雑です。
サーバ運営・管理的には難易度が高い部類になるかと思います。
20221223_mailserver_2022enhance.jpg


上図に(丁寧に作っている暇がないので)手書きで示した構成図を見ても、実に8種類のサーバサイドソフトウェアを使って1つのメールサーバを実現しています。しかも、これは基本的構成に過ぎません。

実際は、このほかにも動作させるために、Perl,ruby,Python といったスクリプト言語や、rust などの新しい言語のライブラリ、各種の補助的ツールを必要とし、メーリングリストの実現なんかは更に、上図で図示していない別のサーバソフトウェアを必要とします。
また、SSL/TLS といった暗号化通信のためには、サーバ証明書の取得・管理だけではなく、OpenSSL,GnuTLS といった暗号化ライブラリが別途必須になります。

こういう構成図(設計図?)を描ける若手エンジニアを昨今では見ませんね。
「クラウド」「クラウド」と、もてはやすおかげで、自力でこのようなものを構築する機会を奪っているから。
ハッキリ言って、この状況では日本のICT技術水準は、自ら周回遅れの状況を作ってしまっています。

ということで、技術継承的な危機感を感じていますが、憂いでも何も変わらないので、せめて構成例の一例として参考にして欲しいということで、自分メモも兼ねて記録に残しています。(つか、こっちがメインかも)

抑えておきたいポート番号とSSL/TLS の対応

皆さまが使う、電子メール送受信ソフトウェア(Outlook,Thunderbird など)にて、サーバの設定におけるポート番号と暗号化通信形式の組み合わせは、一般的に下記の表のようになります。
時々「メール送受信が上手く出来ない」という問合わせ受けますが、単純ではないものの、下記の対応が滅茶苦茶になっている設定が案外散見されます。参考にどうぞ。
表1 送信側
ポート番号非暗号STARTTLSSSL/TLS説明
25(SMTP)×現在はメールサーバ間の通信に使用。
昨今はOP25 対策で、メール送信元で使えない場合が殆ど。
587(submission)×メール送信元にて、メール発信の際に使う。
OP25環境では、メールサーバ間の通信にも使用。
465(SMTPS)××メール送信元にて、暗号通信を行う際に使用する。
有名どころでは OCN なんかがこれを標準にしているが、
おかげで従来の制御機器が使えなくなるケースも実在する。
表2 受信側
ポート番号非暗号STARTTLSSSL/TLS説明
110(POP3)×メール受信者が、メールサーバから電子メールを取り寄せる時に使用。
143(IMAP4)×POP3 と役割は同じだが、メールサーバ上にメールを一定期間残す挙動が標準。
993(IMAP4S)××IMAP4 を暗号化通信専用にしたようなもの。その他は IMAP4 と同じ。
995(POP3S)××POP3 を暗号化通信専用したようなもの。その他は POP3 と同じ。
有名どころでは OCN なんかがこれを標準にしているが、
おかげで従来の制御機器が使えなくなるケースも実在する。
STARTTLS というのは、一般的に「暗号通信するぞ」という宣言を接続元で行うという手順を踏んで暗号化通信を行う仕組みを指します。
SSL/TLS は、SSL(Secure Sockets Layer) の後継規格が TLS(Transport Layer Security) というもので、SSL は既に旧規格です。ですが、役割は同じなので、同列に語られます。
SSL は当初、Netscape社が開発したものですが、IETF というインターネット技術の国際標準化組織に引き継ぐ際に名称が変わりました。

と、いうことで次からメールサーバ構築の流れを自分メモ的に記していきます。

2022/06/11(土)実験・開発用サーバにSSDを入れてみた

2022/06/11 6:59 サーバ運営・管理
書き込み回数に事実上の制限があるため、サーバ用途にはSSDとか、まともに使えないと思ってはいますが、
プログラム・ライブラリは、読み込みが圧倒的なので、OSとアプリケーションだけをSSDに入れるようにすることで、実用に供するかも? と思い、試験的に採用してみました。

20220611.JPG


「KIOXIA」(キオクシア)というのは、東芝メモリ株式会社の現在の企業名のようです。
つまり、東芝の半導体メモリ部門が社名変更したようなものですね。
東芝の半導体メモリ部門以外の半導体製品群は「東芝デバイス&ストレ―ジ株式会社」となっており、こちらは、従来通り「東芝」を名乗っているようです。

税込み 5,000円未満で入手出来る安物ですが、評判は悪くないので、どの程度の期間使用できるかの確認といった感じで試用中です。

書き込みが頻繁に起きる /var 配下や、ユーザディレクトリ、SWAP をSSDではなく、通常のHDDにパーティションを割り振って、書き込みを最小限に抑えるようにしています。

当然、zfs ファイルシステムは使っていません。ufs なファイルシステムで構成しています。

2022/06/11(土)FreeBSD 13.1R + Perl 5.36.0 の環境で adiaryの更新時エラー

暫定的な場当たり対処なので、とりあえず記事の形で・・・。

提起の通り、FreeBSD 13.1R 上で Perl 5.36.0 の環境で、adiary 3.40c を稼働させて、
おもむろに記事作成や更新を行うと、
[AutoLoader] Can't modify undef operator in scalar assignment at lib/SatsukiApp/adiary_4.pm line 925, near "undef
}"
Compilation failed in require at lib/Satsuki/AutoLoader.pm line 42.
といったエラーが出るようになりました。FreeBSD 13.0R + Perl 5.32 の環境までは発生しない。
OSもPerlも同時に更新したため、
Perl のバージョンアップで挙動が変わったのか、OSで何かあるのかが判らない状態です。

但し、更新そのものは出来ている模様。

これは、lib/SatsukiApp/adiary_4.pm の 913行目~
sub void_plugin_images {
        my $self = shift;
        my $h    = shift;
        my $form = shift;
        foreach(keys(%$form)) {
                if ($_ !~ /^(\w+)_void$/) { next; }
                if (! $form->{$_}) { next; }
                my $n = $1;
                $h->{$n} = undef
                $h->{"${n}_w"} = undef
                $h->{"${n}_h"} = undef
        }
}
と、なっているところを
sub void_plugin_images {
        my $self = shift;
        my $h    = shift;
        my $form = shift;
        foreach(keys(%$form)) {
                if ($_ !~ /^(\w+)_void$/) { next; }
                if (! $form->{$_}) { next; }
                my $n = $1;
                $h->{$n} = undef ;
                $h->{"${n}_w"} = undef ;
                $h->{"${n}_h"} = undef ;
        }
}
とすると、回避出来ました。これが正しいのかどうかは判りません。

2022/03/31(木)postgresql にて、任意の select 文を csv 出力する

自分メモその2。ざっくりと、こんな感じ。
# psql -h サーバFQDN DB名 -U ユーザ名 -c "《任意のselect 文》;" -A -F , > output.csv
サーバFQDN は、ホスト名のほか、IPアドレスでも可能です。自ホストの場合は localhost と指定します。
DB名・ユーザ名は、そのままですね。
任意のselect文は、全体をダブルクォーテーションで括ります。終端には必ずセミコロン「;」を付けます。
select 文中に括弧やダブルクォーテーションが入る場合、エスケープが必要かもしれません。

 -A は、「桁揃えなしのテーブル出力」で、これを指定しないと、CSV 出力の際に余計なスペース等が入ってしまいます。
 -F は、「桁揃えなし出力時のフィールド区切り文字」で、デフォルトの区切り文字は "|" なので、CSV 出力の場合は、必ずカンマ「,」を指定します。

あとは、リダイレクトで出力ファイル名の指定。
文字コードは、DBの文字コードが適用されます。