如何对接Postfix的non_smtp_milter以实现自定义DKIM签名?
兄弟,太懂你折腾OpenDKIM碰壁的憋屈了——DKIM明明是邮件投递的命根子,找个顺手的现成工具咋就这么难!不过你已经摸到了Postfix的non_smtp_milters配置和OpenSSL的签名能力,其实已经站在起跑线上了,我给你捋个实操的入手方向,尽量贴合你偏好的bash方案:
首先得敲个重点:non_smtp_milters是基于Sendmail的Milter协议的,Postfix只是兼容这个接口,不是随便绑定个端口传数据就行的,你的程序得遵循Milter的通信规则来解析Postfix发过来的邮件数据,再返回处理后的结果。
先给你一个bash方向的起步方案
纯bash写Milter服务确实有点挑战(因为要处理二进制协议),但可以用socat这个工具来搭个桥梁,帮你监听端口、转发数据流,你只需要专注在邮件签名的逻辑上:
用socat绑定端口,对接Postfix和你的脚本
先安装socat(大部分发行版的包管理器里都有),然后用下面的命令启动监听:socat TCP-LISTEN:8891,fork EXEC:/path/to/your/dkim-sign-script.sh这个命令会让socat监听本地8891端口,每收到Postfix的Milter请求,就启动你的bash脚本,把Milter的数据流通过标准输入/输出传递给脚本处理。
在bash脚本里处理Milter协议的基础逻辑
Milter的消息是固定格式的:开头4字节是网络字节序的消息长度,后面是实际的消息内容。你的脚本首先得能读取并解析这个:# 读取4字节的长度字段(忽略dd的错误输出) len_bytes=$(dd bs=4 count=1 2>/dev/null) # 把二进制长度转成十进制(网络字节序转主机字节序) len=$(echo -n "$len_bytes" | od -An -tu4 -N4 | tr -d ' ') # 读取对应长度的消息内容 msg=$(dd bs="$len" count=1 2>/dev/null)这里用了
dd读二进制数据,od做格式转换,纯bash搞二进制确实有点绕,但凑合用是没问题的。提取邮件内容,生成DKIM签名
接下来你要从Milter消息里提取邮件的头和正文,然后按照DKIM的规范对内容做规范化处理:- 邮件头:要挑选DKIM要求的字段(比如From、To、Date、Subject等),按特定顺序排列,去掉多余的空格和换行
- 邮件正文:要统一换行格式(比如把CRLF转成LF),去掉末尾的空白行
然后用你熟悉的OpenSSL命令生成签名:
# 生成正文的SHA256哈希(DKIM的bh字段) body_hash=$(echo -n "$normalized_body" | openssl dgst -sha256 -binary | base64) # 生成要签名的DKIM原始内容(头字段+正文哈希) dkim_data="$normalized_headers||$body_hash" # 用私钥签名,生成b字段 dkim_signature=$(echo -n "$dkim_data" | openssl dgst -sha256 -sign /path/to/your/private.key | base64)最后把这些字段拼成标准的
DKIM-Signature头:DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=yourdomain.com; s=your_selector; h=From:To:Date; bh=$body_hash; b=$dkim_signature把签名头插回邮件,返回给Postfix
按照Milter协议,你需要用ADDHEADER命令把生成的DKIM头插入到邮件里,然后返回“继续处理”的指令给Postfix。这部分需要你按照Milter的消息格式构造响应,再输出到标准输出(socat会把它传给Postfix)。
一些实用的注意事项
- 纯bash处理Milter的二进制协议容易踩坑,比如邮件的编码、多部分邮件的处理、协议消息的错误处理等。如果遇到瓶颈,可以考虑用Python/Perl写个轻量的Milter服务,然后在服务里调用你的bash签名脚本,这样兼顾bash的灵活性和脚本语言处理协议的便利性。
- 测试的时候,先把Postfix的
non_smtp_milters配置改成inet:localhost:8891,重启Postfix后发测试邮件,然后看/var/log/mail.log(或者/var/log/maillog)里的Milter相关日志,检查有没有错误,同时查看收到的邮件头里有没有生成的DKIM签名。 - 一定要严格遵循DKIM的规范来生成签名,比如头字段的顺序、空白的处理、签名的编码,不然签名会验证失败。
备注:内容来源于stack exchange,提问作者user10709800

