備忘録

MySQLでメールアドレスが管理できるようメールサーバを構築

PostfixAdminを使わずにMySQLでメールアドレスが管理できるようメールサーバ(Postfix/Dovecot)を構築した時のメモ

CentOS7にPostfixをインストール

(※本サイトの「CentOS7に最新のPostfix3.4をインストール」を参照ください)

CentOS7にdovecotをインストール

(※本サイトの「CentOS7に最新のdovecot2.3.7.2をインストール」を参照ください)

CentOS7にMySQLをインストール

(※本サイトの「CentOS7にMySQL公式リポジトリを使って最新のMySQL8.0をインストール」を参照ください)

専用ユーザとディレクトリを作成

# groupadd -g 10000 vmail
# useradd -g vmail -u 10000 vmail -s /sbin/nologin -M
# mkdir /var/spool/vmail
# chown vmail:vmail /var/spool/vmail
# chmod 700 /var/spool/vmail

Postfixの設定

/etc/postfix/main.cf

変更前にバックアップ

# cp -p /etc/postfix/main.cf /etc/postfix/main.cf_`date "+%Y%m%d%H%M%S"`
# メールサーバのホスト名
#myhostname = host.domain.tld
#myhostname = virtual.domain.tld
 ↓
myhostname = mail.example.com

# メールのドメイン名
#mydomain = domain.tld
 ↓
mydomain = example.com

# ローカルで配送依頼されたメールの送信元アドレスに付加するドメイン名
#myorigin = $myhostname
#myorigin = $mydomain
 ↓
myorigin = $mydomain

# メールを受け取るサーバ範囲
#inet_interfaces = all
#inet_interfaces = $myhostname
#inet_interfaces = $myhostname, localhost
inet_interfaces = localhost
 ↓
inet_interfaces = all

# プロトコル
#inet_protocols = ipv4
 ↓
inet_protocols = ipv4

# メール配送トランスポート使って配送されるドメインリスト
mydestination = $myhostname, localhost.$mydomain, localhost
#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain,
#mail.$mydomain, www.$mydomain, ftp.$mydomain
 ↓
mydestination = localhost

# メールの保存形式と保存先
#home_mailbox = Mailbox
#home_mailbox = Maildir/
 ↓
home_mailbox = Maildir/

# ヘッダーチェックの有効化
#header_checks = regexp:/etc/postfix/header_checks
 ↓
header_checks = regexp:/etc/postfix/header_checks

# postfixのバージョン情報の非表示
#smtpd_banner = $myhostname ESMTP $mail_name
#smtpd_banner = $myhostname ESMTP $mail_name ($mail_version)
 ↓
smtpd_banner = $myhostname ESMTP unknown
※最終行に追記
# SASL認証
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain = $myhostname

# 古いバージョンのAUTHコマンドの互換(Outlook Express を使用する場合のみ有効にする)
#broken_sasl_auth_clients = yes

# VRFYコマンドの無効
disable_vrfy_command = yes

# HELOコマンドの要求
smtpd_helo_required = yes

# HELOコマンドで通知されたFQDNに応じての接続制限
smtpd_helo_restrictions = permit_mynetworks, reject_invalid_hostname, reject_unknown_client, permit

# クライアントコマンドの接続制限
smtpd_client_restrictions = permit_mynetworks, reject_unknown_client, permit

# MAIL FROMコマンドの接続制限
smtpd_sender_restrictions = reject_unknown_sender_domain

# RCPT TOコマンドの接続制限
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination

# ETRNコマンドの接続制限
smtpd_etrn_restrictions = permit_mynetworks, reject_invalid_hostname

# メールメッセージの上限(20M)
message_size_limit = 20971520

# メールボックスサイズの上限(2G)
mailbox_size_limit = 2147483648

### バーチャルドメイン ###
local_transport = local
virtual_transport = virtual

virtual_uid_maps = static:10000
virtual_gid_maps = static:10000
virtual_minimum_uid = 10000

virtual_mailbox_base = /var/spool/vmail
virtual_alias_maps = mysql:/etc/postfix/mysql/virtual_alias.conf.ext
virtual_alias_domains = $virtual_alias_maps
virtual_mailbox_maps = mysql:/etc/postfix/mysql/virtual_mailbox.conf.ext
virtual_mailbox_domains = mysql:/etc/postfix/mysql/virtual_domains.conf.ext

virtual_mailbox_limit = 2147483648

/etc/postfix/header_checks

変更前にバックアップ

# cp -p /etc/postfix/header_checks /etc/postfix/header_checks_`date "+%Y%m%d%H%M%S"`
### メーラーで拒否 ###
/^X-Mailer:.*DM Mailer/ REJECT
/^X-Mailer:.*Direct Email/ REJECT

### タイトルで拒否 ###
/^Subject:.*</#.*@.*>/ REJECT

### メールアドレスで拒否 ###
/^From:.*</#.*@.*>/ REJECT
/^Return-Path:.*</#.*@.*>/ REJECT

### 添付の拡張子で拒否 ###
/^content-(type|disposition):.*name[ [:space:] ]*=.*\.(ade|adp|apk|appx|appxbundle|bat|cab|chm|cmd|com|cpl|dll|dmg|exe|hta|inf|ins|isp|iso|jar|js|jse|lib|lnk|mde|msc|msi|msix|msixbundle|msp|mst|nsh|pif|reg|scr|sct|shb|shs|sys|swf|vb|vbe|vbs|vxd|wsc|wsf|wsh)/ REJECT

### グリニッジ標準時で拒否(日本は+0900) ###
if /^Date/
!/Date:.*+0900/ REJECT
endif

submission ポートを使って送信するための設定

/etc/postfix/master.cf

変更前にバックアップ

# cp -p /etc/postfix/master.cf /etc/postfix/master.cf_`date "+%Y%m%d%H%M%S"`
#submission inet n       -       n       -       -       smtpd
#  -o syslog_name=postfix/submission
#  -o smtpd_tls_security_level=encrypt
#  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_tls_auth_only=yes
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
#  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
 ↓
submission inet n       -       n       -       -       smtpd
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject

Dovecotの設定

/etc/dovecot/dovecot.conf

変更前にバックアップ

# cp -p /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf_`date "+%Y%m%d%H%M%S"`
#POP3とIMAP4を有効
#protocols = imap pop3 lmtp submission
 ↓
protocols = imap pop3

#IPV4のみ
#listen = *, ::
 ↓
listen = *

#dovecotであることを隠す
#login_greeting = Dovecot ready.
 ↓
login_greeting = ready.

/etc/dovecot/conf.d/10-auth.conf

変更前にバックアップ

# cp -p /etc/dovecot/conf.d/10-auth.conf /etc/dovecot/conf.d/10-auth.conf_`date "+%Y%m%d%H%M%S"`
#SSLなしの認証を許可
#disable_plaintext_auth = yes
 ↓
disable_plaintext_auth = no

#認証時のメカニズム
auth_mechanisms = plain
 ↓
auth_mechanisms = plain login

#コメントアウト
!include auth-system.conf.ext
 ↓
#!include auth-system.conf.ext

/etc/dovecot/conf.d/10-ssl.conf

変更前にバックアップ

# cp -p /etc/dovecot/conf.d/10-ssl.conf /etc/dovecot/conf.d/10-ssl.conf_`date "+%Y%m%d%H%M%S"`
ssl = required
 ↓
#ssl = required

ssl_cert = </etc/pki/dovecot/certs/dovecot.pem
ssl_key = </etc/pki/dovecot/private/dovecot.pem
 ↓
#ssl_cert = </etc/pki/dovecot/certs/dovecot.pem
#ssl_key = </etc/pki/dovecot/private/dovecot.pem

※「10-ssl.conf」ではコメントアウトして、「local.conf」で設定をまとめてます。

複数のファイルを修正する必要があり、管理するのが面倒なのでファイルを作成してまとめて記述します。

/etc/dovecot/local.conf を作成

### 10-mail.conf ###
mail_location = maildir:/var/spool/vmail/%d/%n
first_valid_uid = 10000
first_valid_gid = 10000
mail_plugins = quota

### 10-master.conf ###
service imap-login {
  inet_listener imap {
    port = 143
  }
  inet_listener imaps {
    #port = 993
    #ssl = yes
  }
}

service pop3-login {
  inet_listener pop3 {
    port = 110
  }
  inet_listener pop3s {
    #port = 995
    #ssl = yes
  }
}

service auth {
  # Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
    mode = 0600
    user = postfix
    group = postfix
  }
}

### 10-ssl.conf ###
ssl = yes
ssl_cert = </etc/pki/dovecot/certs/dovecot.pem
ssl_key = </etc/pki/dovecot/private/dovecot.pem

### 20-imap.conf ###
protocol imap {
  imap_client_workarounds = delay-newmail tb-extra-mailbox-sep
  mail_plugins = $mail_plugins imap_quota
}

### 20-pop3.conf ###
protocol pop3 {
  pop3_client_workarounds = outlook-no-nuls oe-ns-eoh
  mail_plugins = $mail_plugins
}

### 90-quota.conf ###
plugin {
  quota = maildir:User quota
}

### auth-sql.conf.ext ###
passdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf
}

userdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf.ext
}

MySQLでメールアドレスを管理

MySQL用の追加モジュールをインストール

# yum --enablerepo=gf-plus install postfix3-mysql
# yum --enablerepo=gf-plus install dovecot23-mysql

データベース・ユーザの作成と権限を設定

MySQLにrootでログイン

# mysql -u root -p

Enter password: [パスワード入力][Enter]

データベースの作成

CREATE DATABASE IF NOT EXISTS [データベース名] CHARACTER SET [文字コード] COLLATE [照合順序];

mysql> CREATE DATABASE IF NOT EXISTS mail_db CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

ユーザの作成

常にrootユーザ使うのは、権限が強すぎてセキュリティ上よくないので、専用ユーザを作成します。

CREATE USER IF NOT EXISTS [ユーザ名]@[ホスト名] IDENTIFIED BY [パスワード];

mysql> CREATE USER IF NOT EXISTS 'mail_user'@'localhost' IDENTIFIED BY 'mailPassword-888';

ユーザに権限を付与

GRANT [権限] ON [データベース名].[対象のテーブル名] TO [ユーザ名]@[ホスト名];

mysql> GRANT SELECT, INSERT, UPDATE, DELETE, LOCK TABLES ON mail_db.* TO 'mail_user'@'localhost';

ドメインテーブルの作成

mysql> USE mail_db;

mysql> CREATE TABLE `domain` (
 `domain_id`    TINYINT UNSIGNED NOT NULL AUTO_INCREMENT,
 `domain_name`  VARCHAR(50) NOT NULL,
 PRIMARY KEY (`domain_id`),
 UNIQUE KEY `domain_index1`(`domain_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

ドメインデータの登録

mysql> INSERT INTO domain (`domain_name`) VALUES ('example.com');

mysql> SELECT * FROM domain;
+-----------+-------------+
| domain_id | domain_name |
+-----------+-------------+
|         1 | example.com |
+-----------+-------------+

メールユーザテーブルの作成

mysql> CREATE TABLE `mail` (
 `mail_id`        SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
 `user_name`      VARCHAR(50) NOT NULL,
 `domain_id`      TINYINT UNSIGNED NOT NULL,
 `password`       VARCHAR(150) NOT NULL,
 `quota_storage`  SMALLINT UNSIGNED NOT NULL DEFAULT '0',
 `quota_messages` SMALLINT UNSIGNED NOT NULL DEFAULT '0',
 `forward`        TEXT NOT NULL,
 PRIMARY KEY (`mail_id`),
 UNIQUE KEY `mail_index1`(`user_name`, `domain_id`),
 CONSTRAINT `mail_index2` FOREIGN KEY (`domain_id`) REFERENCES domain(`domain_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

メールユーザの登録

mysql> INSERT INTO mail (`user_name`, `domain_id`, `password`, `quota_storage`, `quota_messages`, `forward`) VALUES ('test', '1', '{SHA512...省略', 0, 0, 'test@example.com,test@yahoo.co.jp');
mysql> INSERT INTO mail (`user_name`, `domain_id`, `password`, `quota_storage`, `quota_messages`, `forward`) VALUES ('sample', '1', '{SHA512...省略', 2048, 10000, '');
mysql> INSERT INTO mail (`user_name`, `domain_id`, `password`, `quota_storage`, `quota_messages`, `forward`) VALUES ('info', '1', '{SHA512...省略', 2048, 10000, 'test@example.com,sample@example.com');

mysql> SELECT * FROM mail;
+---------+-----------+-----------+----------------+---------------+----------------+-----------------------------------------------------+
| mail_id | user_name | domain_id | password       | quota_storage | quota_messages | forward                                             |
+---------+-----------+-----------+----------------+---------------+----------------+-----------------------------------------------------+
|       1 | test      |         1 | {SHA512...省略 |             0 |              0 | test@example.com,test@yahoo.co.jp                   |
|       2 | sample    |         1 | {SHA512...省略 |          2048 |          10000 |                                                     |
|       3 | info      |         1 | {SHA512...省略 |          2048 |          10000 | test@example.com,sample@example.com                 |
+---------+-----------+-----------+----------------+---------------+----------------+-----------------------------------------------------+

※パスワードを「SHA512-CRYPT」で登録する場合、予めdoveadmコマンドでパスワードのハッシュ値を取得しておきます。

# doveadm pw -s SHA512-CRYPT -p "[パスワード]"

{SHA512...省略

※quota_storage

単位=MB (0 ~ 65535)
2048:2GB
0:無制限

※quota_messages

単位=件数 (0 ~ 65535)
10000:10000件
0:無制限

※message_size_limit、mailbox_size_limit、virtual_mailbox_limit要検討

※forward(転送する場合に設定)

複数の場合はカンマ「,」区切

※転送元にもメールを残す場合は自分のメールアドレスも設定します。

MySQL連携用SQL

ディレクトリ作成

# mkdir -p /etc/postfix/mysql/

/etc/postfix/mysql/virtual_domains.conf.ext

user = mail_user
password = mailPassword-888
hosts = localhost
dbname = mail_db
query = SELECT dm.domain_name FROM domain AS dm WHERE dm.domain_name = '%s'

/etc/postfix/mysql/virtual_mailbox.conf.ext

user = mail_user
password = mailPassword-888
hosts = localhost
dbname = mail_db
query = SELECT dm.domain_name ||  '/' ||  ml.user_name ||  '/' ||  FROM mail AS ml INNER JOIN domain AS dm ON (ml.domain_id = dm.domain_id) WHERE ml.user_name ||  '@' ||  dm.domain_name = '%s';

/etc/postfix/mysql/virtual_alias.conf.ext

user = mail_user
password = mailPassword-888
hosts = localhost
dbname = mail_db
query = SELECT forward FROM mail AS ml INNER JOIN domain AS dm ON (ml.domain_id = dm.domain_id) WHERE ml.user_name ||  '@' ||  dm.domain_name = '%s' AND ml.forward != '';

/etc/dovecot/dovecot-sql.conf.ext

driver = mysql
#default_pass_scheme = PLAIN
default_pass_scheme = SHA512-CRYPT
connect = host=localhost dbname=mail_db user=mail_user password=mailPassword-888
password_query = SELECT password FROM mail AS ml INNER JOIN domain AS dm ON (ml.domain_id = dm.domain_id) WHERE ml.user_name ||  '@' ||  dm.domain_name = '%u';
user_query = SELECT '/var/spool/vmail/%d/%n' AS home, 10000 AS uid, 10000 AS gid, '*:storage=' ||  CAST(ml.quota_storage AS CHAR || , 'M') AS quota_rule, '*:messages=' ||  CAST(ml.quota_messages AS CHAR || ) AS quota_rule2 FROM mail AS ml INNER JOIN domain AS dm ON (ml.domain_id = dm.domain_id) WHERE ml.user_name ||  '@' ||  dm.domain_name = '%u';
iterate_query = SELECT ml.user_name ||  '@' ||  dm.domain_name AS user FROM mail AS ml INNER JOIN domain AS dm ON (ml.domain_id = dm.domain_id);

PostfixとDovecotを再起動

# systemctl restart postfix.service
# systemctl restart dovecot.service

iptablesの設定

(※本サイトの「CentOS7を安全に運用する為の初期設定(iptablesの設定)」を参照ください)

smtp(25)、submission(587)、pop3(110)、imap(143)ポートを許可

/etc/sysconfig/iptables

変更前にバックアップ

# cp -p /etc/sysconfig/iptables /etc/sysconfig/iptables_`date "+%Y%m%d%H%M%S"`
# smtp
-A INPUT -p tcp -m state --state NEW -m tcp --dport 25 -j ACCEPT

# submission
-A INPUT -p tcp -m state --state NEW -m tcp --dport 587 -j ACCEPT

# pop3
-A INPUT -p tcp -m state --state NEW -m tcp --dport 110 -j ACCEPT

# imap
-A INPUT -p tcp -m state --state NEW -m tcp --dport 143 -j ACCEPT

修正後のiptables

*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]

# 確立済みの通信は許可
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 自ホストからの通信は全て許可
-A INPUT -i lo -j ACCEPT

# pingは許可(1秒間に1回の制限)
-A INPUT -p icmp -m limit --limit 1/s --limit-burst 1 -j ACCEPT

# プライベートアドレスを破棄
-A INPUT -s 10.0.0.0/8 -j DROP
-A INPUT -s 172.16.0.0/12 -j DROP
-A INPUT -s 192.168.0.0/16 -j DROP

# ローカルループバックアドレスを破棄
-A INPUT -s 127.0.0.0/8 -j DROP

# リンクローカルアドレスを破棄
-A INPUT -s 169.254.0.0/16 -j DROP

# テストネットワークアドレスを破棄
-A INPUT -s 192.0.2.0/24 -j DROP
-A INPUT -s 198.51.100.0/24 -j DROP
-A INPUT -s 203.0.113.0/24 -j DROP

# クラスDを破棄
-A INPUT -s 224.0.0.0/4 -j DROP

# クラスEを破棄
-A INPUT -s 240.0.0.0/4 -j DROP

# Current networkを破棄
-A INPUT -d 0.0.0.0/8 -j DROP

# ブロードキャストアドレスを破棄
-A INPUT -d 255.255.255.255 -j DROP

# データを持たないパケットを破棄
-A INPUT -p tcp --tcp-flags ALL NONE -j DROP

# SYNフラッド攻撃と思われる接続を破棄
-A INPUT -p tcp -m state --state NEW ! --syn -j DROP

# ステルススキャン攻撃と思われる接続を破棄
-A INPUT -p tcp --tcp-flags ALL ALL -j DROP

# フラグメントパケット攻撃と思われる接続を破棄
-A INPUT -f -j DROP

# ssh
:SSH - [0:0]
:SSH_ATTACK - [0:0]
# -A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
# 同じホストから60秒以内に5回のSSH接続があったら、そのホストからのSSH接続を10分間受け付けない
-A INPUT -p tcp -m state --state NEW -m tcp --dport 10022 -j SSH
-A SSH -m recent --name sshbadcon --rcheck --seconds 600 -j REJECT
-A SSH -m recent --name sshcon --rcheck --seconds 60 --hitcount 5 -j SSH_ATTACK
-A SSH -m recent --name sshcon --set
-A SSH -j ACCEPT
-A SSH_ATTACK -m recent --name sshbadcon --set
-A SSH_ATTACK -j LOG --log-prefix "iptables:SSH_ATTACK: "
-A SSH_ATTACK -j REJECT

# http
-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT

# smtp
-A INPUT -p tcp -m state --state NEW -m tcp --dport 25 -j ACCEPT

# submission
-A INPUT -p tcp -m state --state NEW -m tcp --dport 587 -j ACCEPT

# pop3
-A INPUT -p tcp -m state --state NEW -m tcp --dport 110 -j ACCEPT

# imap
-A INPUT -p tcp -m state --state NEW -m tcp --dport 143 -j ACCEPT

-A INPUT -j LOG --log-prefix "iptables:" --log-level=warning
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT

iptablesの再起動

# systemctl restart iptables.service