Пример #1
0
def test_mail_content():
    """Test mail_content function."""
    with pytest.raises(email.errors.MessageError,
                       message="mail 'None' is not a email.message.Message."):
        imaputils.mail_content(None)
    fmail = open('examples/spam.from.spamassassin.eml', 'rb')
    ftext = fmail.read()
    mail = imaputils.new_message(ftext)
    fmail.close()
    assert isinstance(imaputils.mail_content(mail), (str, bytes))
Пример #2
0
def test_mail_content():
    """Test mail_content function."""
    with pytest.raises(email.errors.MessageError):
        imaputils.mail_content(None)
        pytest.fail("mail 'None' is not a email.message.Message.")
    fmail = open('examples/spam.from.spamassassin.eml', 'rb')
    ftext = fmail.read()
    mail = imaputils.new_message(ftext)
    fmail.close()
    assert isinstance(imaputils.mail_content(mail), (str, bytes))
Пример #3
0
def test_mail(mail, spamc=False, cmd=False):
    """Test a email with spamassassin."""
    score = "0/0\n"
    orig_code = None
    spamassassin_result = None
    returncode = None

    if cmd:
        satest = cmd
    elif spamc:
        # let spamc process mails larger than 500 KB if ISBG forwards them
        satest = ["spamc", "-E", "--max-size=268435450"]
    else:
        satest = ["spamassassin", "--exit-code"]

    proc = utils.popen(satest)

    try:
        spamassassin_result = proc.communicate(imaputils.mail_content(mail))[0]
        returncode = proc.returncode
        proc.stdin.close()
        score = utils.score_from_mail(spamassassin_result.decode(errors='ignore'))

    except Exception:  # pylint: disable=broad-except
        score = "-9999"

    return score, returncode, spamassassin_result
Пример #4
0
def test_mail(mail, spamc=False, cmd=False):
    """Test a email with spamassassin."""
    score = "0/0\n"
    orig_code = None
    spamassassin_result = None
    returncode = None

    if cmd:
        satest = cmd
    elif spamc:
        satest = ["spamc", "-E"]
    else:
        satest = ["spamassassin", "--exit-code"]

    proc = utils.popen(satest)

    try:
        spamassassin_result = proc.communicate(imaputils.mail_content(mail))[0]
        returncode = proc.returncode
        proc.stdin.close()
        score = utils.score_from_mail(
            spamassassin_result.decode(errors='ignore'))

    except Exception:  # pylint: disable=broad-except
        score = "-9999"

    return score, returncode, spamassassin_result
Пример #5
0
    def _process_spam(self, uid, score, mail, spamdeletelist, code,
                      spamassassin_result):
        self.logger.debug(__("{} is spam".format(uid)))

        if (self.deletehigherthan is not None
                and float(score.split('/')[0]) > self.deletehigherthan):
            spamdeletelist.append(uid)
            return False

        # do we want to include the spam report
        if self.noreport is False:
            if self.dryrun:
                self.logger.info("Skipping report because of --dryrun")
            else:
                new_mail = spamassassin_result
                if new_mail == u"-9999":
                    self.logger.exception(
                        '{} error for mail {} (ret code {})'.format(
                            self.cmd_save, uid, code))
                    self.logger.debug(repr(imaputils.mail_content(mail)))
                    if uid in spamdeletelist:
                        spamdeletelist.remove(uid)
                    return False

                res = self.imap.append(self.imapsets.spaminbox, None, None,
                                       new_mail)
                # The above will fail on some IMAP servers for various
                # reasons. We print out what happened and continue
                # processing
                if res[0] != 'OK':
                    self.logger.error(
                        __(("{} failed for uid {}: {}. Leaving original" +
                            "message alone.").format(
                                repr([
                                    "append", self.imapsets.spaminbox,
                                    "{email}"
                                ]), repr(uid), repr(res))))
                    if uid in spamdeletelist:
                        spamdeletelist.remove(uid)
                    return False
        else:
            # No report:
            if self.dryrun:
                self.logger.info("Skipping copy to spambox because" +
                                 " of --dryrun")
            else:
                # just copy it as is
                self.imap.uid("COPY", uid, self.imapsets.spaminbox)

        return True
Пример #6
0
    def _process_spam(self, uid, score, mail, spamdeletelist, code, spamassassin_result):
        self.logger.debug(__("{} is spam".format(uid)))

        if (self.deletehigherthan is not None and
                float(score.split('/')[0]) > self.deletehigherthan):
            spamdeletelist.append(uid)
            return False

        # do we want to include the spam report
        if self.noreport is False:
            if self.dryrun:
                self.logger.info("Skipping report because of --dryrun")
            else:
                new_mail = spamassassin_result
                if new_mail == u"-9999":
                    self.logger.exception(
                        '{} error for mail {} (ret code {})'.format(
                            self.cmd_save, uid, code))
                    self.logger.debug(repr(imaputils.mail_content(mail)))
                    if uid in spamdeletelist:
                        spamdeletelist.remove(uid)
                    return False

                res = self.imap.append(self.imapsets.spaminbox, None, None,
                                       new_mail)
                # The above will fail on some IMAP servers for various
                # reasons. We print out what happened and continue
                # processing
                if res[0] != 'OK':
                    self.logger.error(__(
                        ("{} failed for uid {}: {}. Leaving original" +
                         "message alone.").format(
                            repr(["append", self.imapsets.spaminbox,
                                  "{email}"]),
                            repr(uid), repr(res))))
                    if uid in spamdeletelist:
                        spamdeletelist.remove(uid)
                    return False
        else:
            # No report:
            if self.dryrun:
                self.logger.info("Skipping copy to spambox because" +
                                 " of --dryrun")
            else:
                # just copy it as is
                self.imap.uid("COPY", uid, self.imapsets.spaminbox)

        return True
Пример #7
0
def learn_mail(mail, learn_type):
    """Process a email and try to learn or unlearn it.

    Args:
        mail (email.message.Message): email to learn.
        learn_type (str): ```spam``` to learn spam, ```ham``` to learn
            nonspam or ```forget```.
    Returns:
        int, int: It returns a pair of `int`

        The first integer:
            A return code of ``6`` means it was already learned or forgotten,
            a return code of ``5`` means it has been learned or forgotten,
            a ``-9999`` means an error communicating with ``spamc``. If
            ``spamc`` returns an exit code, it returns it.

        The second integer:
            It's the original exit code from ``spamc``

    Notes:
        See `Exit Codes` section of the man page of ``spamc`` for more
        information about other exit codes.

    """
    out = ""
    orig_code = None
    size = len(mail.as_bytes())
    proc = utils.popen(["spamc", "--learntype=" + learn_type, "--max-size="+str(size+1)])
    try:
        out = proc.communicate(imaputils.mail_content(mail))
        code = int(proc.returncode)
        orig_code = code
    except Exception:  # pylint: disable=broad-except
        code = -9999

    proc.stdin.close()

    if code == 0:
        out = out[0].decode(errors='ignore').strip()
        if out == __spamc_msg__['already']:
            code = 6
        elif out == __spamc_msg__['success']:
            code = 5

    return code, orig_code
Пример #8
0
def learn_mail(mail, learn_type):
    """Process a email and try to learn or unlearn it.

    Args:
        mail (email.message.Message): email to learn.
        learn_type (str): ```spam``` to learn spam, ```ham``` to learn
            nonspam or ```forget```.
    Returns:
        int, int: It returns a pair of `int`

        The first integer:
            A return code of ``6`` means it was already learned or forgotten,
            a return code of ``5`` means it has been learned or forgotten,
            a ``-9999`` means an error communicating with ``spamc``. If
            ``spamc`` returns an exit code, it returns it.

        The second integer:
            It's the original exit code from ``spamc``

    Notes:
        See `Exit Codes` section of the man page of ``spamc`` for more
        information about other exit codes.

    """
    out = ""
    orig_code = None
    proc = utils.popen(["spamc", "--learntype=" + learn_type])
    try:
        out = proc.communicate(imaputils.mail_content(mail))
        code = int(proc.returncode)
        orig_code = code
    except Exception:  # pylint: disable=broad-except
        code = -9999

    proc.stdin.close()

    if code == 0:
        out = out[0].decode(errors='ignore').strip()
        if out == __spamc_msg__['already']:
            code = 6
        elif out == __spamc_msg__['success']:
            code = 5

    return code, orig_code
Пример #9
0
def feed_mail(mail, spamc=False, cmd=False):
    """Feed a email with spamassassin report."""
    new_mail = ""
    orig_code = None

    if cmd:
        sasave = cmd
    elif spamc:
        sasave = ["spamc"]
    else:
        sasave = ["spamassassin"]

    proc = utils.popen(sasave)
    try:
        new_mail = proc.communicate(imaputils.mail_content(mail))[0]
        orig_code = proc.returncode
    except Exception:  # pylint: disable=broad-except
        new_mail = u"-9999"

    proc.stdin.close()

    return new_mail, orig_code
Пример #10
0
    def learn(self, folder, learn_type, move_to, origpastuids):
        """Learn the spams (and if requested deleted or move them).

        Args:
            folder (str): The IMAP folder.
            leart_type (str): ```spam``` to learn spam, ```ham``` to learn
                nonspam.
            move_to (str): If not ```None```, the imap folder where the emails
                will be moved.
            origpastuids (list(int)): ``uids`` to not process.
        Returns:
            Sa_Learn:
                It contains the information about the result of the process.

            It will call ``spamc`` to learn the emails.

        Raises:
            isbg.ISBGError: if learn_type is unknown.


        TODO:
            Add suport to ``learn_type=forget``.

        """
        sa_learning = Sa_Learn()

        # Sanity checks:
        if learn_type not in ['spam', 'ham']:
            raise isbg.ISBGError(-1, message="Unknown learn_type")
        if self.imap is None:
            raise isbg.ISBGError(-1, message="Imap is required")

        self.logger.debug(__(
            "Teach {} to SA from: {}".format(learn_type, folder)))

        self.imap.select(folder)
        if self.learnunflagged:
            _, uids = self.imap.uid("SEARCH", None, "UNFLAGGED")
        elif self.learnflagged:
            _, uids = self.imap.uid("SEARCH", None, "(FLAGGED)")
        else:
            _, uids = self.imap.uid("SEARCH", None, "ALL")

        uids, sa_learning.newpastuids = SpamAssassin.get_formated_uids(
            uids, origpastuids, self.partialrun)

        sa_learning.tolearn = len(uids)

        for uid in uids:
            mail = imaputils.get_message(self.imap, uid, logger=self.logger)

            # Unwrap spamassassin reports
            unwrapped = sa_unwrap.unwrap(mail)
            if unwrapped is not None:
                self.logger.debug(__("{} Unwrapped: {}".format(
                    uid, utils.shorten(imaputils.mail_content(
                        unwrapped[0]), 140))))

            if unwrapped is not None and unwrapped:  # len(unwrapped)>0
                mail = unwrapped[0]

            if self.dryrun:
                self.logger.warning("Skipped learning due to dryrun!")
                continue
            else:
                code, code_orig = learn_mail(mail, learn_type)

            if code == -9999:  # error processing email, try next.
                self.logger.exception(__(
                    'spamc error for mail {}'.format(uid)))
                self.logger.debug(repr(imaputils.mail_content(mail)))
                continue

            if code in [69, 74]:
                raise isbg.ISBGError(
                    isbg.__exitcodes__['flags'],
                    "spamassassin is misconfigured (use --allow-tell)")

            if code == 5:  # learned.
                sa_learning.learned += 1
                self.logger.debug(__(
                    "Learned {} (spamc return code {})".format(uid,
                                                               code_orig)))

            elif code == 6:  # already learned.
                self.logger.debug(__(
                    "Already learned {} (spamc return code {})".format(
                        uid, code_orig)))

            elif code == 98:  # too big.
                self.logger.warning(__(
                    "{} is too big (spamc return code {})".format(
                        uid, code_orig)))

            else:
                raise isbg.ISBGError(-1, ("{}: Unknown return code {} from " +
                                          "spamc").format(uid, code_orig))

            sa_learning.uids.append(int(uid))

            if not self.dryrun:
                if self.learnthendestroy:
                    if self.gmail:
                        self.imap.uid("COPY", uid, "[Gmail]/Trash")
                    else:
                        self.imap.uid("STORE", uid, self.spamflagscmd,
                                      "(\\Deleted)")
                elif move_to is not None:
                    self.imap.uid("COPY", uid, move_to)
                elif self.learnthenflag:
                    self.imap.uid("STORE", uid, self.spamflagscmd,
                                  "(\\Flagged)")

        return sa_learning
Пример #11
0
    def learn(self, folder, learn_type, move_to, origpastuids):
        """Learn the spams (and if requested deleted or move them).

        Args:
            folder (str): The IMAP folder.
            leart_type (str): ```spam``` to learn spam, ```ham``` to learn
                nonspam.
            move_to (str): If not ```None```, the imap folder where the emails
                will be moved.
            origpastuids (list(int)): ``uids`` to not process.
        Returns:
            Sa_Learn:
                It contains the information about the result of the process.

            It will call ``spamc`` to learn the emails.

        Raises:
            isbg.ISBGError: if learn_type is unknown.


        TODO:
            Add suport to ``learn_type=forget``.

        """
        sa_learning = Sa_Learn()

        # Sanity checks:
        if learn_type not in ['spam', 'ham']:
            raise isbg.ISBGError(-1, message="Unknown learn_type")
        if self.imap is None:
            raise isbg.ISBGError(-1, message="Imap is required")

        self.logger.debug(
            __("Teach {} to SA from: {}".format(learn_type, folder)))

        self.imap.select(folder)
        if self.learnunflagged:
            _, uids = self.imap.uid("SEARCH", None, "UNFLAGGED")
        elif self.learnflagged:
            _, uids = self.imap.uid("SEARCH", None, "(FLAGGED)")
        else:
            _, uids = self.imap.uid("SEARCH", None, "ALL")

        uids, sa_learning.newpastuids = SpamAssassin.get_formated_uids(
            uids, origpastuids, self.partialrun)

        sa_learning.tolearn = len(uids)

        for uid in uids:
            mail = imaputils.get_message(self.imap, uid, logger=self.logger)

            # Unwrap spamassassin reports
            unwrapped = sa_unwrap.unwrap(mail)
            if unwrapped is not None:
                self.logger.debug(
                    __("{} Unwrapped: {}".format(
                        uid,
                        utils.shorten(imaputils.mail_content(unwrapped[0]),
                                      140))))

            if unwrapped is not None and unwrapped:  # len(unwrapped)>0
                mail = unwrapped[0]

            if self.dryrun:
                self.logger.warning("Skipped learning due to dryrun!")
                continue
            else:
                code, code_orig = learn_mail(mail, learn_type)

            if code == -9999:  # error processing email, try next.
                self.logger.exception(__(
                    'spamc error for mail {}'.format(uid)))
                self.logger.debug(repr(imaputils.mail_content(mail)))
                continue

            if code in [69, 74]:
                raise isbg.ISBGError(
                    isbg.__exitcodes__['flags'],
                    "spamassassin is misconfigured (use --allow-tell)")

            if code == 5:  # learned.
                sa_learning.learned += 1
                self.logger.debug(
                    __("Learned {} (spamc return code {})".format(
                        uid, code_orig)))

            elif code == 6:  # already learned.
                self.logger.debug(
                    __("Already learned {} (spamc return code {})".format(
                        uid, code_orig)))

            elif code == 98:  # too big.
                self.logger.warning(
                    __("{} is too big (spamc return code {})".format(
                        uid, code_orig)))

            else:
                raise isbg.ISBGError(-1, ("{}: Unknown return code {} from " +
                                          "spamc").format(uid, code_orig))

            sa_learning.uids.append(int(uid))

            if not self.dryrun:
                if self.learnthendestroy:
                    if self.gmail:
                        self.imap.uid("COPY", uid, "[Gmail]/Trash")
                    else:
                        self.imap.uid("STORE", uid, self.spamflagscmd,
                                      "(\\Deleted)")
                elif move_to is not None:
                    self.imap.uid("COPY", uid, move_to)
                elif self.learnthenflag:
                    self.imap.uid("STORE", uid, self.spamflagscmd,
                                  "(\\Flagged)")

        return sa_learning