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)
问题原因
- 索引错误:
chgheader的索引参数从0开始计数,原代码使用1会导致索引越界,尤其是处理第一个重复头时,不存在索引为1的头。 - 线程安全问题:全局
seen_message_ids集合会被多线程共享,不同邮件请求互相干扰,导致误判重复。 - 处理阶段限制:在
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
相关产品推荐
相关产品推荐

