SpamExperts/OrangeAssassin

View on GitHub
oa/plugins/razor2.py

Summary

Maintainability
A
1 hr
Test Coverage
"""Razor2 check plugin."""

import threading
import subprocess

import oa.plugins.base


def kill_process(process, log):
    log.debug("Razor timed out")
    process.kill()


class Razor2Plugin(oa.plugins.base.BasePlugin):
    eval_rules = ("check_razor2",
                  "check_razor2_range")

    options = {"use_razor2": ("bool", True),
               "razor_timeout": ("int", 5),
               "razor_config": ("str", "")
               }

    def check_razor2_range(self, msg, engine, min, max, target=None):
        """
        Not implemented. Use pyzor in order to check range conditions.
        :param msg:
        :param engine:
        :param min:
        :param max:
        :param target:
        :return:
        """
        pass

    def check_razor2(self, msg, full="", target=None):
        """ Checks a mail against the distributed Razor Catalogue
        by communicating with a Razor Catalogue Server.
            If we have returncode = 1 => it's not a spam
            If we have returncode = 0 => it's a spam
        :param msg: Message to be check
        :param full: Not used
        :param target: "None" by default
        :return:True if the message is listed on Rayzor
        """
        if not self["use_razor2"]:
            return 0

        try:
            return self.get_local(msg, "razor2_result")
        except KeyError:
            pass
        self.set_local(msg, "razor2_result", 0)

        if not self["razor_config"]:
            args = ["razor-check"]
        else:
            conf_arg = "-conf=" + self["razor_config"]
            args = ["razor-check", conf_arg]
        try:
            proc = subprocess.Popen(args, stderr=subprocess.PIPE,
                                    stdin=subprocess.PIPE,
                                    stdout=subprocess.PIPE)
        except OSError as e:
            self.ctxt.log.error("Unable to run razor-check: %s", e)
            return

        my_timer = threading.Timer(self["razor_timeout"], kill_process,
                                   [proc, self.ctxt.log])
        try:
            my_timer.start()
            proc.communicate(input=str.encode(msg.raw_msg))
        finally:
            my_timer.cancel()

        if proc.returncode in (1, 0):
            self.set_local(msg, "razor2_result", proc.returncode)
        self.ctxt.log.debug(proc.returncode)
        return not proc.returncode

    def launch_subprocess(self, msg, name):
        if not self["razor_config"]:
            args = [name]
        else:
            conf_arg = "-conf=" + self["razor_config"]
            args = [name, conf_arg]

        try:
            proc = subprocess.Popen(args, stderr=subprocess.PIPE,
                                    stdin=subprocess.PIPE,
                                    stdout=subprocess.PIPE)
        except OSError:
            self.ctxt.log.warning("Unable to run " + name)
            return

        my_timer = threading.Timer(self["razor_timeout"], kill_process,
                                   [proc, self.ctxt.log])
        my_timer.start()
        try:
            proc.communicate(input=str.encode(msg.raw_msg))
            return proc.returncode
        except (IOError, OSError):
            self.ctxt.log.warning("Unable to communicate to " + name)
        finally:
            my_timer.cancel()

        return False

    def plugin_report(self, msg):
        """Report the message to razor server as spam."""

        self.launch_subprocess(msg, "razor-report")

    def plugin_revoke(self, msg):
        """Report the message to razor server as ham."""

        self.launch_subprocess(msg, "razor-revoke")