通过Python的SpamAssassin并不总是有效?

时间:2017-12-05 20:44:16

标签: python email spamassassin

我通过smtpd设置了一个由Python脚本管理的邮件服务器,因为我收到了一些垃圾邮件,所以我决定将SpamAssassin连接到它。

由于我找不到连接到SpamAssassin的Python代码来获得分数,我自己使用我在网上发现的一些内容构建了它。这是代码:

# -*- config:utf-8 -*-

import socket, select, re, logging
from io import BytesIO


divider_pattern = re.compile(br'^(.*?)\r?\n(.*?)\r?\n\r?\n', re.DOTALL)
first_line_pattern = re.compile(br'^SPAMD/[^ ]+ 0 EX_OK$')


# @see https://github.com/slimta/python-slimta/blob/master/slimta/policy/spamassassin.py
class SpamAssassin(object):
    def __init__(self, message, timeout=15):
        self.score = None
        self.symbols = None

        # Connecting
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client.settimeout(timeout)
        client.connect(('127.0.0.1', 783))

        # Sending
        client.sendall(self._build_message(message))
        client.shutdown(socket.SHUT_WR)

        # Reading
        resfp = BytesIO()
        while True:
            ready = select.select([client], [], [], timeout)
            if ready[0] is None:
                # Kill with Timeout!
                logging.info('[SpamAssassin] - Timeout ({0}s)!'.format(str(timeout)))
                break

            data = client.recv(4096)
            if data == b'':
                break

            resfp.write(data)

        # Closing
        client.close()
        client = None

        self._parse_response(resfp.getvalue())

    def _build_message(self, message):
        reqfp = BytesIO()
        data_len = str(len(message)).encode()
        reqfp.write(b'SYMBOLS SPAMC/1.2\r\n')
        reqfp.write(b'Content-Length: ' + data_len + b'\r\n')
        reqfp.write(b'User: cx42\r\n\r\n')
        reqfp.write(message)
        return reqfp.getvalue()

    def _parse_response(self, response):
        if response == b'':
            logging.info("[SPAM ASSASSIN] Empty response")
            return None

        match = divider_pattern.match(response)
        if not match:
            logging.error("[SPAM ASSASSIN] Response error:")
            logging.error(response)
            return None

        first_line = match.group(1)
        headers = match.group(2)
        body = response[match.end(0):]

        # Checking response is good
        match = first_line_pattern.match(first_line)
        if not match:
            logging.error("[SPAM ASSASSIN] invalid response:")
            logging.error(first_line)
            return None

        self.symbols = [s.decode('ascii').strip() for s in body.strip().split(',')]

        headers = headers.replace(' ', '').replace(':', ';').replace('/', ';').split(';')
        self.score = float(headers[2])

    def get_score(self):
        return self.score

    def get_symbols(self):
        return self.symbols

    def is_spam(self, level=5):
        return self.score is None or self.score >= level

在我的服务器脚本上,我有以下部分来检查垃圾邮件:

# data is the mail body received from smtpd
assassin = SpamAssassin(data)
if assassin.is_spam():
    logging.info('SpamAssassin rejected. Score of {0}'.format(assassin.get_score()))
    return '554 Command rejected for policy reasons.'

如果代码未进入if状态,则会发送邮件。

我遇到的一个巨大问题是发送了一些电子邮件,即使它们被认为是来自SpamAssassin的垃圾邮件。

我知道,因为我已经构建了第二个脚本,它从postfix加载队列(通过postqueue -j获取JSON格式),并从我的代码中执行SpamAssassin检查。然后,相当多的电子邮件被检测为垃圾邮件。 (为了不显示太多代码,加载Postfix队列并清除它的是here)。

我不知道这里显示的代码有什么问题,我的Python代码如何允许发送垃圾邮件。

我检查了日志,我的代码中没有任何例外(例如来自SpamAssassin的超时或其他任何内容)。

对我来说,问题是条件if assassin.is_spam()会返回False而在某些情况下会返回True,但我不知道如何/为什么/何时,所以我求助于你。

我的理论是:

  • 也许套接字重新使用了某个缓存版本,该版本在新电子邮件中为SpamAssassin返回False,并且无法正确检查
  • 使用socket / spamd.py文件发生了一些奇怪的事情,因为它是唯一似乎不起作用的地方。
  • 可能是一些并发问题?由于在服务器上发出了很多请求,可能会为多个传入请求打开一个套接字,并且会读取所有传入请求的第一个结果,接受不应该发送的邮件吗?

0 个答案:

没有答案