Điều quan trọng nhất với mailserver là IP sạch, không lọt vào blacklist của các tổ chức chống spam, nếu không các email được gởi đi đều bị trả lại, hoặc may mắn hơn thì được gởi đi và bị đánh dấu spam.
Các ISP có thể cung cấp IP tĩnh theo yêu cầu nhưng chỉ để tiện tạo webserver, không chắc là IP sạch, và cũng không thể đổi lại nếu lại dính vào blacklist. Vậy giải pháp tiện hơn là nhờ (relay) các mailserver có uy tín như mailserver của Google hay Microsoft gởi mail dùm, khi đó chúng ta cũng không cần đến IP tĩnh. Nếu cần gởi mail số lượng nhiều thì có thể dùng các smtp relay chuyên nghiệp như mailjet.com
Tạo CSDL
Để thiết kế relayhost, có thể dùng tập tin text (và mã hóa) hay dùng CSDL. Chúng ta dùng CSDL MySQL:
## Tạo bàng relayhost và relayhost_auth ##
mysql -h localhost -D mailserver <<EOQ
CREATE TABLE IF NOT EXISTS relayhost ( sender varchar(100) NOT NULL, host varchar(100) NOT NULL, PRIMARY KEY (sender) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS relayhost_auth ( id tinyint NOT NULL auto_increment, sender varchar(100) NOT NULL, user varchar(100) NOT NULL, password varchar(150) NOT NULL, working tinyint default 0, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
EOQ
Bảng relayhost chứa các dòng ánh xạ sender và relayhost
sender host
@example.org [smtp.gmail.com]:587
@another.ltd [smtp.gmail.com]:587
Bảng relayhost_auth chứa các dòng ánh xạ sender và user trên relayhost
sender user password working
@example.org user1@gmail.com nshxcgfertsjz 0
@example.org user2@gmail.com qwrf[thdnvnas 1
@example.org user3@gmail.com sd.vge[pkfnwf -1
@another.ltd user4@gmail.com eoerk[p3it34q 1
@another.ltd user5@gmail.com pekrjgseqswdr 0
Việc dùng sender trong cả 2 bảng thay vì sender và host, để dùng trong trường hợp mailserver quản lý nhiều domain.
Để Postfix gởi email qua relayhost
Chúng ta cần tạo 2 tập tin .cf để chỉ dẫn cách đọc CSDL cho Postfix. Chúng ta cần dùng mật khẩu $svr_pwd của mailserver (trong file ~/readme.txt)
## mysql_relayhost_maps.cf ##
cat > /etc/postfix/mysql_relayhost_maps.cf <<EOF
hosts = 127.0.0.1:3306
user = mailserver
password = $svr_pwd
dbname = mailserver
query = SELECT host FROM relayhost WHERE sender='%s' LIMIT 1;
EOF
## msql_relayhost_auth ##
cat > /etc/postfix/msql_relayhost_auth <<EOF
hosts = 127.0.0.1:3306
user = mailserver
password = $svr_pwd
dbname = mailserver
query = SELECT concat(user, ':', password) FROM relayhost_auth WHERE sender='%s' AND working=1 LIMIT 1;
EOF
## Cấu hình main.cf ##
postconf sender_dependent_relayhost_maps=mysql:/etc/postfix/mysql_relayhost_maps.cf
postconf smtp_sasl_password_maps=mysql:/etc/postfix/mysql_relayhost_auth.cf
# Enable STARTTLS encryption
postconf smtp_use_tls = yes
# enable SASL authentication
postconf smtp_sasl_auth_enable = yes
postconf smtp_sasl_mechanism_filter = login
# disallow methods that allow anonymous authentication.
postconf smtp_sasl_security_options = noanonymous
postconf smtp_sender_dependent_authentication = yes
Trong bảng relayhost_auth, một sender có nhiều relayhost thì chỉ có một relayhost đang được dùng (=1), các host khác chờ đến phiên (=0) hay dự trữ (=-1). Để luân phiên dùng relayhost, chúng ta dùng Store Procedure rotate_relayhost_auth:
mysql -h localhost -D mailserver -e '
DELIMITER $$
CREATE PROCEDURE `rotate_relayhost_auth`(IN val INT)
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE nRows INTEGER;
DECLARE rsender VARCHAR(100);
DEClARE curNumRows
CURSOR FOR
SELECT sender, COUNT(*) FROM relayhost_auth WHERE working != -1 GROUP BY sender;
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET finished = 1;
PREPARE stmt FROM "SELECT id INTO @cId FROM relayhost_auth WHERE working != -1 AND sender = ? ORDER BY id LIMIT ?, 1;";
OPEN curNumRows;
getSender: LOOP
FETCH curNumRows INTO rsender, nRows;
IF finished = 1 THEN
LEAVE getSender;
END IF;
IF nRows < 2 THEN
ITERATE getSender;
END IF;
EXECUTE stmt USING rsender, MOD(val, nRows);
UPDATE relayhost_auth SET working = CASE WHEN id = @cId THEN 1 WHEN id != @cId AND working != -1 AND sender = rsender THEN 0 ELSE working END;
END LOOP getSender;
CLOSE curNumRows;
DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;'
Store procedure này được gọi qua crontab mỗi n giờ.
0 */n * * * mysql -h localhost -D mailserver -e "call rotate_relayhost_auth(round(hour(curtime())/n))"
User abc@example.org gởi mail relay qua google.com (thí dụ nhờ vào xyz@gmail.com) thì mail chuyển đến người nhận với sender là xyz. Muốn người nhận thấy sender là abc thì cần làm thêm vài bước, mỗi relay server có cách khác nhau.
Script
#!/bin/bash
# O1.relayhost.sh
# Cài đặt relayhost cho mailserver
# © 2020 LNT <lnt@ly-le.info>
# version 20200801
#
HOSTNAME=$(hostname)
DOMAIN=${HOSTNAME#*.}
SEL_PWD=$(sed -n '/mailselect/{n;s/\s*password: \(.*\)/\1/;p;q}' ~/${DOMAIN}.txt)
mysql -h localhost -D mailserver <<EOQ
SET NAMES utf8;
SET time_zone = '+07:00';
DROP TABLE IF EXISTS relayhost;
CREATE TABLE relayhost (
sender varchar(60) NOT NULL,
host varchar(60) NOT NULL,
PRIMARY KEY (sender)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO relayhost (sender, host) VALUES ('@${DOMAIN}', '[smtp.gmail.com]:587');
DROP TABLE IF EXISTS relayhost_auth;
CREATE TABLE relayhost_auth (
id tinyint NOT NULL AUTO_INCREMENT,
sender varchar(60) NOT NULL,
user varchar(60) NOT NULL,
password varchar(100) NOT NULL,
working tinyint not null DEFAULT 0,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP PROCEDURE IF EXISTS rotate_relayhost_auth;
DELIMITER $$
CREATE PROCEDURE rotate_relayhost_auth (IN val INT)
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE nRows INTEGER;
DECLARE rsender VARCHAR(60);
DEClARE curNumRows
CURSOR FOR
SELECT sender, COUNT(*) FROM relayhost_auth WHERE working != -1 GROUP BY sender;
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET finished = 1;
PREPARE stmt FROM "SELECT id INTO @cId FROM relayhost_auth WHERE working != -1 AND sender = ? ORDER BY id LIMIT ?, 1;";
OPEN curNumRows;
getSender: LOOP
FETCH curNumRows INTO rsender, nRows;
IF finished = 1 THEN
LEAVE getSender;
END IF;
IF nRows < 2 THEN
ITERATE getSender;
END IF;
EXECUTE stmt USING rsender, MOD(val, nRows);
UPDATE relayhost_auth SET working = CASE WHEN id = @cId THEN 1 WHEN id != @cId AND working != -1 AND sender = rsender THEN 0 ELSE working END;
END LOOP getSender;
CLOSE curNumRows;
DEALLOCATE PREPARE stmt;
END$$
DELIMITER ;
EOQ
## mysql_relayhost_maps.cf ##
cat > /etc/postfix/mysql-relayhost_maps.cf <<EOF
hosts = 127.0.0.1
user = mailselect
password = ${SEL_PWD}
dbname = mailserver
query = SELECT host FROM relayhost WHERE sender='%s' LIMIT 1;
EOF
## msql_relayhost_auth ##
cat > /etc/postfix/mysql-relayhost_auth.cf <<EOF
hosts = 127.0.0.1
user = mailselect
password = ${SEL_PWD}
dbname = mailserver
query = SELECT concat(user, ':', password) FROM relayhost_auth WHERE sender='%s' AND working=1 LIMIT 1;
EOF
## Cấu hình main.cf ##
# relayhost
postconf sender_dependent_relayhost_maps=mysql:/etc/postfix/mysql-relayhost_maps.cf
postconf smtp_sasl_password_maps=mysql:/etc/postfix/mysql-relayhost_auth.cf
postconf smtp_sasl_auth_enable=yes
postconf smtp_sasl_mechanism_filter=login
postconf smtp_sasl_security_options=noanonymous
postconf smtp_sender_dependent_authentication=yes
#postconf smtp_use_tls=yes
# end relayhost
systemctl restart postfix