You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Postfix环境下Python Milter无法修改邮件Header问题求助

解决Postfix Milter无法删除重复Message-ID头的问题

问题背景

有多台服务器生成的自动化邮件通过Postfix MTA发送,生成邮件的软件未严格遵循RFC规范,会产生带重复Message-ID头的邮件且该软件无法修改。编写Python Milter守护进程试图移除重复Message-ID,但调用chgheader时抛出错误:

2023-02-03 18:22:44,983 ERROR Error removing from: Message-ID error message: cannot change header

原代码

import Milter
import logging

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

file_handler = logging.FileHandler('/var/log/milter.log')
file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
logger.addHandler(file_handler)

seen_message_ids = set()

class MessageIDMilter(Milter.Base):
    def __init__(self):
        self.id = Milter.uniqueID()

    @Milter.noreply
    def connect(self, IPname, family, hostaddr):
        logger.debug("Milter connected to %s family %s at address %s" % (IPname, family, hostaddr))
        self.IPname = IPname
        self.family = family
        self.hostaddr = hostaddr
        return Milter.CONTINUE

    @Milter.noreply
    def header(self, name, hval):
        logger.debug("Received header %s with value %s" % (name, hval))
        if name.lower() == "message-id":
            logger.debug("Found message ID: %s" % hval)
            if hval in seen_message_ids:
                logger.debug("Deleting duplicate message ID: %s" % hval)
                try:
                    self.chgheader(name, 1, "")
                except Exception as e:
                    logger.error("Error removing from: %s error message: %s" % (name, e))


            else:
                seen_message_ids.add(hval)
        return Milter.CONTINUE

    @Milter.noreply
    def eoh(self):
        logger.debug("Reached end of headers")
        return Milter.ACCEPT

if __name__ == "__main__":
    logger.debug("Script started OK")
    Milter.factory = MessageIDMilter
    Milter.runmilter("message-id-milter", "inet:10001@localhost", 0)

问题原因

  1. 索引错误:chgheader的索引参数从0开始计数,原代码使用1会导致索引越界,尤其是处理第一个重复头时,不存在索引为1的头。
  2. 线程安全问题:全局seen_message_ids集合会被多线程共享,不同邮件请求互相干扰,导致误判重复。
  3. 处理阶段限制:在header回调中修改当前正在处理的头,Milter不允许这种实时修改操作。

修复后的代码

import Milter
import logging

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

file_handler = logging.FileHandler('/var/log/milter.log')
file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
logger.addHandler(file_handler)

class MessageIDMilter(Milter.Base):
    def __init__(self):
        self.id = Milter.uniqueID()
        # 每个邮件实例独立维护Message-ID集合和头列表
        self.message_ids = set()
        self.collected_headers = []

    @Milter.noreply
    def connect(self, IPname, family, hostaddr):
        logger.debug("Milter connected to %s family %s at address %s" % (IPname, family, hostaddr))
        self.IPname = IPname
        self.family = family
        self.hostaddr = hostaddr
        return Milter.CONTINUE

    @Milter.noreply
    def header(self, name, hval):
        # 收集所有头信息,暂不处理
        self.collected_headers.append((name, hval))
        if name.lower() == "message-id":
            logger.debug("Found message ID: %s" % hval)
        return Milter.CONTINUE

    @Milter.noreply
    def eoh(self):
        logger.debug("Reached end of headers, processing duplicate Message-IDs")
        filtered_headers = []
        
        # 筛选保留唯一的Message-ID和其他所有头
        for name, hval in self.collected_headers:
            if name.lower() == "message-id":
                if hval not in self.message_ids:
                    filtered_headers.append((name, hval))
                    self.message_ids.add(hval)
                    logger.debug("Keeping unique message ID: %s" % hval)
                else:
                    logger.debug("Skipping duplicate message ID: %s" % hval)
            else:
                filtered_headers.append((name, hval))
        
        # 先删除所有原Message-ID头
        self.chgheader('Message-ID', -1, '')
        # 重新添加筛选后的所有头
        for name, hval in filtered_headers:
            self.addheader(name, hval)
        
        return Milter.ACCEPT

if __name__ == "__main__":
    logger.debug("Script started OK")
    Milter.factory = MessageIDMilter
    Milter.runmilter("message-id-milter", "inet:10001@localhost", 0)

关键修复说明

  • 线程安全:每个邮件实例使用独立的message_ids集合,避免多线程间的数据污染。
  • 处理时机调整:在eoh(头结束)阶段处理,此时所有头已收集完成,可安全进行修改操作。
  • 正确删除头:使用chgheader('Message-ID', -1, '')一次性删除所有Message-ID头,避免索引错误;之后重新添加筛选后的唯一Message-ID和其他头。
  • 头收集逻辑:先完整收集所有头,再进行筛选,确保邮件结构的完整性。

内容的提问来源于stack exchange,提问作者btongeorge

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 15:10:40