今回実装したPolicyサービスの動き
①クライアントがRCPTコマンドを実行
②Postfixが標準入力で以下の情報をプログラムに渡す
例)
request=smtpd_access_policy
protocol_state=RCPT
protocol_name=SMTP
helo_name=some.domain.tld
queue_id=8045F2AB23
sender=foo@bar.tld
recipient=bar@foo.tld
client_address=1.2.3.4
client_name=another.domain.tld
instance=123.456.7
sasl_method=plain
sasl_username=you
sasl_sender=
ccert_subject=solaris9.porcupine.org
ccert_issuer=Wietse Venema
ccert_fingerprint=C2:9D:F4:87:71:73:73:D9:18:E7:C2:F3:C1:DA:6E:04
size=12345
[empty line]
③プログラムはPostfixから渡される以下情報を利用してリレー判定をする。
接続元IP(client_address)
SMTP認証ユーザー情報(sasl_username)
接続元IPの情報を、DBに問い合わせて接続を許可するNWか判定する。
④プログラムは判定結果を標準出力に以下のデータを返す。
リレー拒否する場合
action=reject ERROR MESSAGE
[empty line]
リレー拒否しない場合
actions=dunno
[empty line]
actions=には、Postfixのaccessテーブルで利用可能なアクションが設定できる。
Postfix manual - access(5)
dunnoは、テーブルにマッチするものがない場合と同じ動き。
こうすれば、smtpd_recipient_restrictionsで定義した以降のフィルタも評価される。
テスト環境構築
main.cf
smtpd_recipient_restrictions = check_policy_service unix:private/policy, permit_sasl_authenticated, reject_unauth_destination
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous
broken_sasl_auth_clients = yes
smtpd_sasl_local_domain = $myhostname
smtp_sasl_path = smtpd
master.cf
policy unix - n n - - spawn user=nobody argv=/bin/python /usr/libexec/postfix/smtpd-policy-chk.py
/usr/libexec/postfix/smtpd-policy-chk.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import MySQLdb
import ipaddress
import syslog
import time
def output_log(mes):
syslog.openlog()
syslog.syslog("DEBUG: " + mes)
syslog.closelog()
def get_attr():
attr = dict()
while True:
ent = raw_input()
if ent:
if "=" in ent:
k = ent.split("=")[0]
v = ent.split("=")[1]
attr[k] = v
output_log("Input: " + ent)
else:
output_log("Invalid entry: " + ent)
else:
output_log("Fin input" + ent)
break
return attr
def chk_relay(client_address, sasl_username):
connector = MySQLdb.connect(host="localhost", db="testdb", user="testuser", passwd="password", charset="utf8")
cursor = connector.cursor()
sql = "select network from access where user = '" + sasl_username + "'"
cursor.execute(sql)
result = cursor.fetchall()
cursor.close()
connector.close()
if result:
network = result[0][0]
nw = ipaddress.ip_network(network)
ip = ipaddress.ip_address(unicode(client_address, "utf-8"))
if ip in nw:
return "relay"
else:
return "no_relay"
else:
return "no_relay"
def main():
attr = get_attr()
if attr.has_key("client_address") and attr.has_key("sasl_username"):
client_address = attr["client_address"]
sasl_username = attr["sasl_username"].split("@")[0]
if client_address and sasl_username:
ret = chk_relay(client_address, sasl_username)
if ret == "relay":
output_log("Result 1")
sys.stdout.write("action=dunno\n\n")
else:
output_log("Result 2")
sys.stdout.write("action=reject Dameyo\n\n")
else:
output_log("Result 3")
sys.stdout.write("action=dunno\n\n")
else:
output_log("Result 4")
sys.stdout.write("action=dunno\n\n")
exit()
if __name__ == '__main__':
main()
テストデータ
mysql
> create database testdb;
> grant all on testdb.* to testuser@localhost;
> flush privileges;
> set password for testuser@localhost=password('password');
> use testdb
> create table access (user VARCHAR(32), network VARCHAR(64));
> insert into access (user, network) values("test1", "127.0.0.1/32");
> insert into access (user, network) values("test2", "172.31.27.95/32");
> create table user (user VARCHAR(32), password VARCHAR(64), maildir VARCHAR(64));
> insert into user (user, password) values("test1", "password");
> insert into user (user, password) values("test2", "password");
> select * from user;
+-------+----------+---------+
| user | password | maildir |
+-------+----------+---------+
| test1 | password | NULL |
| test2 | password | NULL |
+-------+----------+---------+
2 rows in set (0.00 sec)
> select * from access;
+-------+-----------------+
| user | network |
+-------+-----------------+
| test1 | 127.0.0.1/32 |
| test2 | 172.31.27.95/32 |
+-------+-----------------+
2 rows in set (0.00 sec)