Example #1
0
 def _problemcode(self):
     retcode=string_to_actioncode(self.config.get(self.section,'problemaction'), self.config)
     if retcode!=None:
         return retcode
     else:
         #in case of invalid problem action
         return DEFER
Example #2
0
    def examine(self, suspect):
        if not self.should_we_check_this_domain(suspect):
            return DUNNO
        envelope_recipient_domain = suspect.to_domain.lower()
        envelope_sender_domain = suspect.from_domain.lower()
        if envelope_sender_domain == envelope_recipient_domain:
            return DUNNO  # we only check the message if the env_sender_domain differs. If it's the same it will be caught by other means (like SPF)

        header_from_domain = extract_from_domain(suspect)
        if header_from_domain is None:
            self._logger().warn("%s: Could not extract header from domain for spearphish check" % suspect.id)
            return DUNNO

        if header_from_domain == envelope_recipient_domain:
            virusname = self.config.get(self.section, 'virusname')
            virusaction = self.config.get(self.section, 'virusaction')
            actioncode = string_to_actioncode(virusaction, self.config)

            logmsg = '%s: spear phish pattern detected, recipient=%s env_sender_domain=%s header_from_domain=%s' % (
            suspect.id, suspect.to_address, envelope_sender_domain, header_from_domain)
            self._logger().info(logmsg)
            self.flag_as_phish(suspect, virusname)

            message = apply_template(
                self.config.get(self.section, 'rejectmessage'), suspect, {'virusname': virusname})
            return actioncode, message

        return DUNNO
Example #3
0
    def examine(self, suspect):
        if suspect.get_tag('Dspam.skip') is True:
            self.logger.debug(
                '%s Skipping Dspam Plugin (requested by previous plugin)' %
                suspect.id)
            suspect.set_tag('Dspam.skipreason', 'requested by previous plugin')
            return DUNNO

        if self.config.getboolean(self.section, 'scanoriginal'):
            content = suspect.get_original_source()
        else:
            content = suspect.get_source()

        recipient = suspect.to_address
        try:
            results = self._check_dspam(content, recipient)
        except DspamClientError as e:
            self.logger.error('%s failed to process by dspam: %s' %
                              (suspect.id, str(e)))
            return self._problemcode()

        action = DUNNO
        message = None
        isspam = self._is_spam(results)
        suspect.tags['spam']['Dspam'] = isspam
        suspect.tags['DSpam.report'] = self._report(results)
        if isspam:
            action = string_to_actioncode(
                self.config.get(self.section, 'spamaction'), self.config)
            message = apply_template(
                self.config.get(self.section, 'rejectmessage'), suspect,
                results)

        return action, message
Example #4
0
    def examine(self, suspect):
        self.filelist.filename = self.config.get(self.section, 'domainsfile')
        checkdomains = self.filelist.get_list()

        envelope_sender_domain = suspect.from_domain.lower()
        header_from_domain = extract_from_domain(suspect)
        if header_from_domain is None:
            return

        if header_from_domain not in checkdomains:
            return

        # TODO: do we need a tag from dkim to check if the verified dkim domain
        # actually matches the header from domain?
        dkimresult = suspect.get_tag('DKIMVerify.sigvalid', False)
        if dkimresult == True:
            return DUNNO

        # DKIM failed, check SPF if envelope senderdomain belongs to header
        # from domain
        spfresult = suspect.get_tag('SPF.status', 'unknown')
        if (envelope_sender_domain == header_from_domain or envelope_sender_domain.endswith('.%s' % header_from_domain)) and spfresult == 'pass':
            return DUNNO

        failaction = self.config.get(self.section, 'failaction')
        actioncode = string_to_actioncode(failaction, self.config)

        values = dict(
            header_from_domain=header_from_domain)
        message = apply_template(
            self.config.get(self.section, 'rejectmessage'), suspect, values)
        return actioncode, message
Example #5
0
    def examine(self, suspect):
        self.filelist.filename = self.config.get(self.section, 'domainsfile')
        checkdomains = self.filelist.get_list()

        envelope_sender_domain = suspect.from_domain.lower()
        header_from_domain = self.extract_from_domain(suspect)
        if header_from_domain == None:
            return

        if header_from_domain not in checkdomains:
            return

        # TODO: do we need a tag from dkim to check if the verified dkim domain
        # actually matches the header from domain?
        dkimresult = suspect.get_tag('DKIMVerify.sigvalid', False)
        if dkimresult == True:
            return DUNNO

        # DKIM failed, check SPF if envelope senderdomain belongs to header
        # from domain
        spfresult = suspect.get_tag('SPF.status', 'unknown')
        if (envelope_sender_domain == header_from_domain or envelope_sender_domain.endswith('.%s' % header_from_domain)) and spfresult == 'pass':
            return DUNNO

        failaction = self.config.get(self.section, 'failaction')
        actioncode = string_to_actioncode(failaction, self.config)

        values = dict(
            header_from_domain=header_from_domain)
        message = apply_template(
            self.config.get(self.section, 'rejectmessage'), suspect, values)
        return actioncode, message
Example #6
0
 def examine(self, suspect):
     urls = suspect.get_tag('black.uris', defaultvalue=[])
     add_header_links = self.config.getboolean(self.section,
                                               'addheaderlinks')
     add_header_count = self.config.getboolean(self.section,
                                               'addheadercount')
     if len(urls) == 0:
         return DUNNO
     elif add_header_count is True and add_header_links is False:
         suspect.add_header('X-Black-Host-Count',
                            str(len(urls)),
                            immediate=True)
     elif add_header_count is True and add_header_links is True:
         suspect.add_header('X-Black-Host',
                            "\t" + "\r\n\t\t\t  ".join(urls),
                            immediate=True)
         suspect.add_header('X-Black-Host-Count',
                            str(len(urls)),
                            immediate=True)
     elif add_header_count is False and add_header_links is True:
         suspect.add_header('X-Black-Host',
                            "\t" + "\r\n\t\t\t  ".join(urls),
                            immediate=True)
     return string_to_actioncode(
         self.config.get('URIExtractPlugin', 'action'),
         self.config), apply_template(
             self.config.get('URIExtractPlugin', 'message'), suspect,
             dict(domain=urls[0], blacklist='tbd'))
 def load_limiter_config(self, text):
     patt = re.compile(
         r'^limit\s+name=(?P<name>[^\s]+)\s+rate=(?P<max>\-?\d{1,10})\/(?P<time>\d{1,10})\s+fields=(?P<fieldlist>[^\s]+)(\s+match=\/(?P<matchregex>.+)\/(\s+skip=(?P<skiplist>[^\s]+))?)?\s+action=(?P<action>[^\s]+)\s+message=(?P<message>.*)$'
     )
     limiters = []
     lineno = 0
     for line in text.split('\n'):
         lineno += 1
         line = line.strip()
         if line.startswith('#') or line.strip() == '':
             continue
         match = patt.match(line)
         if match == None:
             self.logger.error('cannot parse limiter config line %s' %
                               lineno)
             continue
         gdict = match.groupdict()
         limiter = Limiter()
         limiter.name = gdict['name']
         limiter.max = int(gdict['max'])
         limiter.timespan = int(gdict['time'])
         limiter.fields = gdict['fieldlist'].split(',')
         limiter.regex = gdict['matchregex']
         if gdict['skiplist'] != None:
             limiter.skip = gdict['skiplist'].split(',')
         action = string_to_actioncode(gdict['action'])
         if action == None:
             self.logger.error(
                 "Limiter config line %s : invalid action %s" %
                 (lineno, gdict['action']))
         limiter.action = action
         limiter.message = gdict['message']
         limiters.append(limiter)
     return limiters
    def examine(self, suspect):
        if suspect.size > self.config.getint(self.section, 'maxsize'):
            self._logger().info('Not scanning - message too big (message %s  bytes > config %s bytes )' %
                                (suspect.size, self.config.getint(self.section, 'maxsize')))
            return DUNNO

        try:
            viruses = self.scan_file(suspect.tempfile)
            if viruses is not None:
                self._logger().info("Virus found in message from %s : %s" %
                                    (suspect.from_address, viruses))
                suspect.tags['virus'][self.config.get(self.section,'identifier')] = True
                suspect.tags['%s.virus'%self.config.get(self.section,'identifier')] = viruses
                suspect.debug('Viruses found in message : %s' % viruses)
            else:
                suspect.tags['virus'][self.config.get(self.section,'identifier')] = False

            if viruses != None:
                virusaction = self.config.get(self.section, 'virusaction')
                actioncode = string_to_actioncode(virusaction, self.config)
                firstinfected, firstvirusname = viruses.items()[0]
                values = dict(
                    infectedfile=firstinfected, virusname=firstvirusname)
                message = apply_template(
                    self.config.get(self.section, 'rejectmessage'), suspect, values)
                return actioncode, message
            else:
                return DUNNO
        except Exception, e:
            self._logger().warning("Error encountered while running cmdline av scan: %s" % str(e))
Example #9
0
    def examine(self, suspect):

        enginename = self.config.get(self.section, 'enginename')

        if suspect.size > self.config.getint(self.section, 'maxsize'):
            self.logger.info('Not scanning - message too big')
            return

        content = suspect.get_source()

        for i in range(0, self.config.getint(self.section, 'retries')):
            try:
                viruses = self.scan_stream(content)
                if viruses != None:
                    self.logger.info(
                        "Virus found in message from %s : %s" % (suspect.from_address, viruses))
                    suspect.tags['virus'][enginename] = True
                    suspect.tags['%s.virus' % enginename] = viruses
                    suspect.debug('viruses found in message : %s' % viruses)
                else:
                    suspect.tags['virus'][enginename] = False

                if viruses != None:
                    virusaction = self.config.get(self.section, 'virusaction')
                    actioncode = string_to_actioncode(virusaction, self.config)
                    firstinfected, firstvirusname = viruses.items()[0]
                    values = dict(
                        infectedfile=firstinfected, virusname=firstvirusname)
                    message = apply_template(
                        self.config.get(self.section, 'rejectmessage'), suspect, values)
                    return actioncode, message
                return DUNNO
            except Exception, e:
                self.logger.warning("Error encountered while contacting ICAP server (try %s of %s): %s" % (
                    i + 1, self.config.getint(self.section, 'retries'), str(e)))
Example #10
0
    def examine(self, suspect):

        if suspect.size > self.config.getint(self.section, 'maxsize'):
            self.logger.info('Not scanning - message too big')
            return

        content = suspect.get_source()

        for i in range(0, self.config.getint(self.section, 'retries')):
            try:
                viruses = self.scan_stream(content)
                if viruses != None:
                    self.logger.info(
                        "Virus found in message from %s : %s" % (suspect.from_address, viruses))
                    suspect.tags['virus']['ClamAV'] = True
                    suspect.tags['ClamavPlugin.virus'] = viruses
                    suspect.debug('viruses found in message : %s' % viruses)
                else:
                    suspect.tags['virus']['ClamAV'] = False

                if viruses != None:
                    virusaction = self.config.get(self.section, 'virusaction')
                    actioncode = string_to_actioncode(virusaction, self.config)
                    firstinfected, firstvirusname = viruses.items()[0]
                    values = dict(
                        infectedfile=firstinfected, virusname=firstvirusname)
                    message = apply_template(
                        self.config.get(self.section, 'rejectmessage'), suspect, values)
                    return actioncode, message
                return DUNNO
            except Exception, e:
                self.logger.warning("Error encountered while contacting clamd (try %s of %s): %s" % (
                    i + 1, self.config.getint(self.section, 'retries'), str(e)))
Example #11
0
    def examine(self, suspect):
        if not self.should_we_check_this_domain(suspect):
            return DUNNO
        envelope_recipient_domain = suspect.to_domain.lower()
        envelope_sender_domain = suspect.from_domain.lower()
        if envelope_sender_domain == envelope_recipient_domain:
            return DUNNO  # we only check the message if the env_sender_domain differs. If it's the same it will be caught by other means (like SPF)

        header_from_domain = extract_from_domain(suspect)
        if header_from_domain is None:
            self._logger().warn(
                "%s: Could not extract header from domain for spearphish check"
                % suspect.id)
            return DUNNO

        if header_from_domain == envelope_recipient_domain:
            virusname = self.config.get(self.section, 'virusname')
            virusaction = self.config.get(self.section, 'virusaction')
            actioncode = string_to_actioncode(virusaction, self.config)

            logmsg = '%s: spear phish pattern detected, recipient=%s env_sender_domain=%s header_from_domain=%s' % (
                suspect.id, suspect.to_address, envelope_sender_domain,
                header_from_domain)
            self._logger().info(logmsg)
            self.flag_as_phish(suspect, virusname)

            message = apply_template(
                self.config.get(self.section, 'rejectmessage'), suspect,
                {'virusname': virusname})
            return actioncode, message

        return DUNNO
Example #12
0
    def check_sql_blacklist(self, suspect, runtimeconfig=None):
        """Check this message against the SQL blacklist. returns highspamaction on hit, DUNNO otherwise"""
        #work in progress
        if not self.config.has_option(
                self.section,
                'check_sql_blacklist') or not self.config.getboolean(
                    self.section, 'check_sql_blacklist'):
            return DUNNO

        if not SQL_EXTENSION_ENABLED:
            self.logger.error(
                'Cannot check sql blacklist, SQLALCHEMY extension is not available'
            )
            return DUNNO

        try:
            dbsession = get_session(
                self.config.get(self.section, 'sql_blacklist_dbconnectstring'))
            conf_sql = self.config.get(self.section, 'sql_blacklist_sql')
            sql, params = self._replace_sql_params(suspect, conf_sql)
            resultproxy = dbsession.execute(sql, params)
        except Exception as e:
            self.logger.error('Could not read blacklist from DB: %s' % e)
            suspect.debug('Blacklist check failed: %s' % e)
            return DUNNO

        for result in resultproxy:
            dbvalue = result[0]  # this value might have multiple words
            allvalues = dbvalue.split()
            for blvalue in allvalues:
                self.logger.debug(blvalue)
                # build regex
                # translate glob to regexr
                # http://stackoverflow.com/questions/445910/create-regex-from-glob-expression
                regexp = re.escape(blvalue).replace(r'\?',
                                                    '.').replace(r'\*', '.*?')
                self.logger.debug(regexp)
                pattern = re.compile(regexp)

                if pattern.search(suspect.from_address):
                    self.logger.debug(
                        '%s Blacklist match : %s for sa pref %s' %
                        (suspect.id, suspect.from_address, blvalue))
                    confcheck = self.config
                    if runtimeconfig is not None:
                        confcheck = runtimeconfig
                    configaction = string_to_actioncode(
                        confcheck.get(self.section, 'highspamaction'),
                        self.config)
                    suspect.tags['spam']['SpamAssassin'] = True
                    prependheader = self.config.get('main',
                                                    'prependaddedheaders')
                    suspect.addheader("%sBlacklisted" % prependheader, blvalue)
                    suspect.debug('Sender is Blacklisted: %s' % blvalue)
                    if configaction is None:
                        return DUNNO
                    return configaction

        return DUNNO
Example #13
0
 def _problemcode(self):
     retcode = string_to_actioncode(
         self.config.get(self.section, 'problemaction'), self.config)
     if retcode != None:
         return retcode
     else:
         # in case of invalid problem action
         return DEFER
Example #14
0
File: sa.py Project: gryphius/fuglu
    def check_sql_blacklist(self, suspect, runtimeconfig=None):
        """Check this message against the SQL blacklist. returns highspamaction on hit, DUNNO otherwise"""
        #work in progress
        if not self.config.has_option(self.section, 'check_sql_blacklist') or not self.config.getboolean(self.section, 'check_sql_blacklist'):
            return DUNNO

        from fuglu.extensions.sql import ENABLED
        if not ENABLED:
            self.logger.error(
                'Cannot check sql blacklist, SQLALCHEMY extension is not available')
            return DUNNO

        from fuglu.extensions.sql import get_session

        try:
            dbsession = get_session(
                self.config.get(self.section, 'sql_blacklist_dbconnectstring'))
            conf_sql = self.config.get(self.section, 'sql_blacklist_sql')

            sql, params = self._replace_sql_params(suspect, conf_sql)

            resultproxy = dbsession.execute(sql, params)
        except Exception as e:
            self.logger.error('Could not read blacklist from DB: %s' % e)
            suspect.debug('Blacklist check failed: %s' % e)
            return DUNNO

        for result in resultproxy:
            dbvalue = result[0]  # this value might have multiple words
            allvalues = dbvalue.split()
            for blvalue in allvalues:
                self.logger.debug(blvalue)
                # build regex
                # translate glob to regexr
                # http://stackoverflow.com/questions/445910/create-regex-from-glob-expression
                regexp = re.escape(blvalue).replace(
                    r'\?', '.').replace(r'\*', '.*?')
                self.logger.debug(regexp)
                pattern = re.compile(regexp)

                if pattern.search(suspect.from_address):
                    self.logger.debug(
                        'Blacklist match : %s for sa pref %s' % (suspect.from_address, blvalue))
                    confcheck = self.config
                    if runtimeconfig != None:
                        confcheck = runtimeconfig
                    configaction = string_to_actioncode(
                        confcheck.get(self.section, 'highspamaction'), self.config)
                    suspect.tags['spam']['SpamAssassin'] = True
                    prependheader = self.config.get(
                        'main', 'prependaddedheaders')
                    suspect.addheader("%sBlacklisted" % prependheader, blvalue)
                    suspect.debug('Sender is Blacklisted: %s' % blvalue)
                    if configaction == None:
                        return DUNNO
                    return configaction

        return DUNNO
Example #15
0
 def test_defaultcodes(self):
     """test actioncode<->string conversion"""
     conf = ConfigParser()
     conf.add_section('spam')
     conf.add_section('virus')
     conf.set('spam', 'defaultlowspamaction', 'REJEcT')
     conf.set('spam', 'defaulthighspamaction', 'REjECT')
     conf.set('virus', 'defaultvirusaction', 'rejeCt')
     self.assertEqual(
         string_to_actioncode('defaultlowspamaction', conf), REJECT)
     self.assertEqual(
         string_to_actioncode('defaulthighspamaction', conf), REJECT)
     self.assertEqual(
         string_to_actioncode('defaultvirusaction', conf), REJECT)
     self.assertEqual(string_to_actioncode('nonexistingstuff'), None)
     self.assertEqual(actioncode_to_string(REJECT), 'REJECT')
     self.assertEqual(
         actioncode_to_string(string_to_actioncode('discard')), 'DELETE')
Example #16
0
 def test_defaultcodes(self):
     """test actioncode<->string conversion"""
     conf = ConfigParser()
     conf.add_section('spam')
     conf.add_section('virus')
     conf.set('spam', 'defaultlowspamaction', 'REJEcT')
     conf.set('spam', 'defaulthighspamaction', 'REjECT')
     conf.set('virus', 'defaultvirusaction', 'rejeCt')
     self.assertEqual(string_to_actioncode('defaultlowspamaction', conf),
                      REJECT)
     self.assertEqual(string_to_actioncode('defaulthighspamaction', conf),
                      REJECT)
     self.assertEqual(string_to_actioncode('defaultvirusaction', conf),
                      REJECT)
     self.assertEqual(string_to_actioncode('nonexistingstuff'), None)
     self.assertEqual(actioncode_to_string(REJECT), 'REJECT')
     self.assertEqual(actioncode_to_string(string_to_actioncode('discard')),
                      'DELETE')
Example #17
0
    def examine(self, suspect):
        starttime = time.time()

        if suspect.size > self.config.getint(self.section, 'maxsize'):
            self._logger().info(
                'Not scanning - message too big (message %s  bytes > config %s bytes )'
                % (suspect.size, self.config.getint(self.section, 'maxsize')))
            return DUNNO

        content = suspect.get_message_rep().as_string()

        for i in range(0, self.config.getint(self.section, 'retries')):
            try:
                headerscannername = '-' + self.config.get(
                    self.section, 'headerscannername')
                if headerscannername == '-':
                    headerscannername = ''
                if self.config.getboolean(self.section, 'networkmode'):
                    viruses = self.scan_stream(content)
                else:
                    viruses = self.scan_file(suspect.tempfile)
                if viruses != None:
                    self._logger().info("Virus found in message from %s : %s" %
                                        (suspect.from_address, viruses))
                    suspect.tags['virus']['F-Prot'] = True
                    suspect.tags['FprotPlugin.virus'] = viruses
                    suspect.debug('Viruses found in message : %s' % viruses)
                else:
                    suspect.tags['virus']['F-Prot'] = False
                    addheaderclean = self.config.get(self.section,
                                                     'addheaderclean')
                    suspect.set_tag('fprot.clean', 'Clean')
                if viruses != None:
                    virusaction = self.config.get(self.section, 'virusaction')
                    actioncode = string_to_actioncode(virusaction, self.config)
                    addheaderinfected = self.config.get(
                        self.section, 'addheaderinfected')
                    firstinfected, firstvirusname = list(viruses.items())[0]
                    values = dict(infectedfile=firstinfected,
                                  virusname=firstvirusname)
                    message = apply_template(
                        self.config.get(self.section, 'rejectmessage'),
                        suspect, values)
                    suspect.set_tag('fprot.virus', firstvirusname)
                    return actioncode, message
                else:
                    return DUNNO
            except Exception as e:
                self._logger().warning(
                    "Error encountered while contacting fpscand (try %s of %s): %s"
                    % (i + 1, self.config.getint(self.section,
                                                 'retries'), str(e)))
        self._logger().error("fpscand failed after %s retries" %
                             self.config.getint(self.section, 'retries'))
        content = None
        return self._problemcode()
Example #18
0
    def examine(self,suspect):
        if not YARA_AVAILABLE:
            return

        self.reload_if_necessary()

        if not self.compiled_rules:
            return

        yarahits = None
        infectedfile = None

        m = suspect.get_message_rep()
        for i in m.walk():
            if i.is_multipart():
                continue
            att_name = i.get_filename(None)

            if att_name:
                # some filenames are encoded, try to decode
                try:
                    att_name = ''.join([x[0] for x in decode_header(att_name)])
                except:
                    pass
            else:
                att_name='message'

            payload = StringIO(i.get_payload(decode=True))
            yarahits = self.check_file(suspect,att_name, payload)
            if yarahits != None:
                infectedfile = att_name
                break

        if infectedfile and yarahits:
            self.logger.info(
                "YARA hit(s) in message from %s : %s" % (suspect.from_address, yarahits))
            suspect.tags['virus']['YARA'] = True
            suspect.tags['YARAPlugin.virus'] = yarahits[0].rule
            suspect.debug('YARA hit found in message : %s' % yarahits)

            virusaction = self.config.get(self.section, 'virusaction')
            actioncode = string_to_actioncode(virusaction, self.config)
            values = dict(
                infectedfile=infectedfile, virusname=yarahits[0].rule)
            message = apply_template(
                self.config.get(self.section, 'rejectmessage'), suspect, values)
            return actioncode, message
        else:
            suspect.tags['virus']['YARA'] = False
            return DUNNO
Example #19
0
    def examine(self, suspect):
        enginename = 'sophos'

        if suspect.size > self.config.getint(self.section, 'maxsize'):
            self.logger.info('Not scanning - message too big')
            return

        content = suspect.get_source()

        for i in range(0, self.config.getint(self.section, 'retries')):
            try:
                viruses = self.scan_stream(content)
                headerscannername = '-' + self.config.get(
                    self.section, 'headerscannername')
                if headerscannername == '-':
                    headerscannername = ''
                if viruses is not None:
                    self.logger.info("Virus found in message from %s : %s" %
                                     (suspect.from_address, viruses))
                    suspect.tags['virus'][enginename] = True
                    suspect.tags['%s.virus' % enginename] = viruses
                    suspect.debug('viruses found in message : %s' % viruses)
                else:
                    suspect.tags['virus'][enginename] = False
                    addheaderclean = self.config.get(self.section,
                                                     'addheaderclean')
                    suspect.set_tag('sssp.clean', 'Clean')
                if viruses is not None:
                    virusaction = self.config.get(self.section, 'virusaction')
                    actioncode = string_to_actioncode(virusaction, self.config)
                    addheaderinfected = self.config.get(
                        self.section, 'addheaderinfected')
                    firstinfected, firstvirusname = list(viruses.items())[0]
                    values = dict(infectedfile=firstinfected,
                                  virusname=firstvirusname)
                    message = apply_template(
                        self.config.get(self.section, 'rejectmessage'),
                        suspect, values)
                    suspect.set_tag('sssp.virus', firstvirusname)
                    return actioncode, message
                return DUNNO
            except Exception as e:
                self.logger.warning(
                    "Error encountered while contacting SSSP server (try %s of %s): %s"
                    % (i + 1, self.config.getint(self.section,
                                                 'retries'), str(e)))
        self.logger.error("SSSP scan failed after %s retries" %
                          self.config.getint(self.section, 'retries'))

        return self._problemcode()
Example #20
0
    def examine(self, suspect):
        if not self.should_we_check_this_domain(suspect):
            return DUNNO
        envelope_recipient_domain = suspect.to_domain.lower()
        envelope_sender_domain = suspect.from_domain.lower()
        if envelope_sender_domain == envelope_recipient_domain:
            return DUNNO  # we only check the message if the env_sender_domain differs. If it's the same it will be caught by other means (like SPF)

        header_from_domains = []
        header_from_domain = extract_from_domain(suspect)
        if header_from_domain is None:
            self.logger.warn(
                "%s: Could not extract header from domain for spearphish check"
                % suspect.id)
            return DUNNO
        else:
            header_from_domains.append(header_from_domain)
            self.logger.debug(
                '%s: checking domain %s (source: From header address part)' %
                (suspect.id, header_from_domain))

        if self.config.getboolean(self.section, 'check_display_part'):
            display_from_domain = extract_from_domain(suspect, False)
            if display_from_domain is not None and display_from_domain not in header_from_domains:
                header_from_domains.append(display_from_domain)
                self.logger.debug(
                    '%s: checking domain %s (source: From header display part)'
                    % (suspect.id, display_from_domain))

        actioncode = DUNNO
        message = None

        for header_from_domain in header_from_domains:
            if header_from_domain == envelope_recipient_domain:
                virusname = self.config.get(self.section, 'virusname')
                virusaction = self.config.get(self.section, 'virusaction')
                actioncode = string_to_actioncode(virusaction, self.config)

                logmsg = '%s: spear phish pattern detected, env_rcpt_domain=%s env_sender_domain=%s header_from_domain=%s' % \
                         (suspect.id, envelope_recipient_domain, envelope_sender_domain, header_from_domain)
                self.logger.info(logmsg)
                self.flag_as_phish(suspect, virusname)

                message = apply_template(
                    self.config.get(self.section, 'rejectmessage'), suspect,
                    {'virusname': virusname})
                break

        return actioncode, message
Example #21
0
    def examine(self, suspect):

        if suspect.size > self.config.getint(self.section, 'maxsize'):
            self.logger.info('Not scanning - message too big')
            return

        content = suspect.get_source()

        for i in range(0, self.config.getint(self.section, 'retries')):
            try:
                viruses = self.scan_stream(content)
		headerscannername = '-' + self.config.get(self.section, 'headerscannername')
                if headerscannername == '-':
                    headerscannername = ''
                if viruses != None:
                    self.logger.info(
                        "Virus found in message from %s : %s" % (suspect.from_address, viruses))
                    suspect.tags['virus']['ClamAV'] = True
                    suspect.tags['ClamavPlugin.virus'] = viruses
                    suspect.debug('viruses found in message : %s' % viruses)
                else:
                    suspect.tags['virus']['ClamAV'] = False
                    addheaderclean = self.config.get(self.section, 'addheaderclean')
                    suspect.set_tag('clam.clean', 'Clean')
                if viruses != None:
                    virusaction = self.config.get(self.section, 'virusaction')
                    actioncode = string_to_actioncode(virusaction, self.config)
                    addheaderinfected = self.config.get(self.section, 'addheaderinfected')
                    firstinfected, firstvirusname = list(viruses.items())[0]
                    values = dict(
                        infectedfile=firstinfected, virusname=firstvirusname)
                    message = apply_template(
                        self.config.get(self.section, 'rejectmessage'), suspect, values)
                    suspect.set_tag('clam.virus', firstvirusname)
                    return actioncode, message
                return DUNNO
            except Exception as e:
                self.__invalidate_socket()

                # don't warn the first time if it's just a broken pipe which
                # can happen with the new pipelining protocol
                if not (i == 0 and isinstance(e, socket.error) and e.errno == errno.EPIPE):
                    self.logger.warning("Error encountered while contacting clamd (try %s of %s): %s" % (
                        i + 1, self.config.getint(self.section, 'retries'), str(e)))

        self.logger.error("Clamdscan failed after %s retries" %
                          self.config.getint(self.section, 'retries'))
        content = None
        return self._problemcode()
Example #22
0
    def lint(self):
        viract = self.config.get(self.section, 'virusaction')
        print("Virusaction: %s" %
              actioncode_to_string(string_to_actioncode(viract, self.config)))
        allok = self.checkConfig() and self.lint_ping() and self.lint_eicar()

        if self.config.getboolean(self.section, 'clamscanfallback'):
            print('WARNING: Fallback to clamscan enabled')
            starttime = time.time()
            allok = self.lint_eicar('shell')
            if allok:
                runtime = time.time() - starttime
                print('clamscan scan time: %.2fs' % runtime)

        return allok
Example #23
0
 def lint(self):
     viract = self.config.get(self.section, 'virusaction')
     print("Virusaction: %s" % actioncode_to_string(
         string_to_actioncode(viract, self.config)))
     allok = self.checkConfig() and self.lint_ping() and self.lint_eicar()
     
     if self.config.getboolean(self.section, 'clamscanfallback'):
         print('WARNING: Fallback to clamscan enabled')
         starttime = time.time()
         allok = self.lint_eicar('shell')
         if allok:
             runtime = time.time()-starttime
             print('clamscan scan time: %.2fs' % runtime)
     
     return allok
Example #24
0
    def examine(self, suspect):
        if not YARA_AVAILABLE:
            return

        self.reload_if_necessary()

        if not self.compiled_rules:
            return

        yarahits = None
        infectedfile = None

        m = suspect.get_message_rep()
        for i in m.walk():
            if i.is_multipart():
                continue
            att_name = i.get_filename(None)

            if att_name:
                # some filenames are encoded, try to decode
                try:
                    att_name = "".join([x[0] for x in decode_header(att_name)])
                except:
                    pass
            else:
                att_name = "message"

            payload = StringIO(i.get_payload(decode=True))
            yarahits = self.check_file(suspect, att_name, payload)
            if yarahits != None:
                infectedfile = att_name
                break

        if infectedfile and yarahits:
            self.logger.info("YARA hit(s) in message from %s : %s" % (suspect.from_address, yarahits))
            suspect.tags["virus"]["YARA"] = True
            suspect.tags["YARAPlugin.virus"] = yarahits[0].rule
            suspect.debug("YARA hit found in message : %s" % yarahits)

            virusaction = self.config.get(self.section, "virusaction")
            actioncode = string_to_actioncode(virusaction, self.config)
            values = dict(infectedfile=infectedfile, virusname=yarahits[0].rule)
            message = apply_template(self.config.get(self.section, "rejectmessage"), suspect, values)
            return actioncode, message
        else:
            suspect.tags["virus"]["YARA"] = False
            return DUNNO
Example #25
0
    def examine(self, suspect):
        if not DOMAINMAGIC_AVAILABLE:
            self.logger.info('Not scanning - Domainmagic not available')
            return DUNNO

        if self.rbllookup is None:
            self.rbllookup = RBLLookup()
            self.rbllookup.from_config(
                self.config.get(self.section, 'blacklistconfig'))
        self._init_tldmagic()

        urls = suspect.get_tag('body.uris', defaultvalue=[])
        #self.logger.info("Body URIs to check: %s"%urls)
        domains = set(map(fqdn_from_uri, urls))

        counter = 0
        for domain in domains:
            counter += 1
            if counter > self.config.getint(self.section, 'maxdomains'):
                self.logger.info("maximum number of domains reached")
                break

            tldcount = self.tldmagic.get_tld_count(domain)
            parts = domain.split('.')

            if self.config.getboolean(self.section, 'checksubdomains'):
                subrange = range(tldcount + 1, len(parts) + 1)
            else:
                subrange = [tldcount + 1]

            for subindex in subrange:
                subdomain = '.'.join(parts[-subindex:])

                listings = self.rbllookup.listings(subdomain)
                for identifier, humanreadable in iter(listings.items()):
                    self.logger.info(
                        "%s : url host %s flagged as %s because %s" %
                        (suspect.id, domain, identifier, humanreadable))
                    return string_to_actioncode(
                        self.config.get(self.section, 'action'),
                        self.config), apply_template(
                            self.config.get(self.section, 'message'), suspect,
                            dict(domain=domain, blacklist=identifier))

        return DUNNO
Example #26
0
    def examine(self, suspect):
        if not DNSQUERY_EXTENSION_ENABLED:
            return DUNNO

        if not self.config.getboolean(self.section, 'check_always'):
            # save the lookup if mail is already tagged as virus or spam
            if suspect.is_virus() or suspect.is_spam() or suspect.is_blocked():
                return DUNNO

        maxlookups = self.config.getint(self.section, 'maxlookups')
        emails = suspect.get_tag('emails', defaultvalue=[])[:maxlookups]
        emails = [self._email_normalise(email) for email in emails]
        emails = list(set(emails))

        #if emails:
        #    self.logger.debug('%s EBL checking addresses %s' % (suspect.id, ', '.join(emails)))

        listed = False
        action = DUNNO
        message = None
        email = None
        for email in emails:
            addr_hash = self._create_hash(email)
            listed, message = self._ebl_lookup(addr_hash)
            if listed:
                break

        suspect.tags['spam']['EBL'] = listed

        if listed:
            self.logger.debug('%s EBL hit for %s' % (suspect.id, email))
            action = string_to_actioncode(
                self.config.get(self.section, 'action'))
            suspect.tags['EBL.email'] = email
            suspect.tags['EBL.reason'] = message
            if action != DUNNO:
                values = {
                    'dnszone': self.config.get(self.section, 'dnszone'),
                    'message': message,
                }
                message = apply_template(
                    self.config.get(self.section, 'messagetemplate'), suspect,
                    values)

        return action, message
Example #27
0
    def examine(self, suspect):
        actionrules = self.config.get(self.section, 'actionrules')
        if actionrules == None or actionrules == "":
            return DUNNO

        if not os.path.exists(actionrules):
            self.logger.error('Action Rules file does not exist : %s' %
                              actionrules)
            return DUNNO

        if self.filter == None:
            self.filter = SuspectFilter(actionrules)

        (match, arg) = self.filter.matches(suspect)
        if match:
            if arg == None or arg.strip() == '':
                self.logger.error("Rule match but no action defined.")
                return DUNNO

            arg = arg.strip()
            spl = arg.split(None, 1)
            actionstring = spl[0]
            message = None
            if len(spl) == 2:
                message = spl[1]
            self.logger.debug("%s: Rule match! Action override: %s" %
                              (suspect.id, arg.upper()))

            actioncode = string_to_actioncode(actionstring, self.config)
            if actioncode != None:
                return actioncode, message

            elif actionstring.upper() == 'REDIRECT':
                suspect.to_address = message.strip()
                suspect.recipients = [
                    suspect.to_address,
                ]
                # todo: should we override to_domain? probably not
                # todo: check for invalid adress, multiple adressses
                # todo: document redirect action
            else:
                self.logger.error("Invalid action: %s" % arg)
                return DUNNO

        return DUNNO
Example #28
0
    def examine(self, suspect):

        if suspect.size > self.config.getint(self.section, 'maxsize'):
            self.logger.info('Not scanning - message too big')
            return

        content = suspect.get_source()

        for i in range(0, self.config.getint(self.section, 'retries')):
            try:
                viruses = self.scan_stream(content)
                if viruses != None:
                    self.logger.info(
                        "Virus found in message from %s : %s" % (suspect.from_address, viruses))
                    suspect.tags['virus']['ClamAV'] = True
                    suspect.tags['ClamavPlugin.virus'] = viruses
                    suspect.debug('viruses found in message : %s' % viruses)
                else:
                    suspect.tags['virus']['ClamAV'] = False

                if viruses != None:
                    virusaction = self.config.get(self.section, 'virusaction')
                    actioncode = string_to_actioncode(virusaction, self.config)
                    firstinfected, firstvirusname = list(viruses.items())[0]
                    values = dict(
                        infectedfile=firstinfected, virusname=firstvirusname)
                    message = apply_template(
                        self.config.get(self.section, 'rejectmessage'), suspect, values)
                    return actioncode, message
                return DUNNO
            except Exception as e:
                self.__invalidate_socket()

                # don't warn the first time if it's just a broken pipe which
                # can happen with the new pipelining protocol
                if not (i == 0 and isinstance(e, socket.error) and e.errno == errno.EPIPE):
                    self.logger.warning("Error encountered while contacting clamd (try %s of %s): %s" % (
                        i + 1, self.config.getint(self.section, 'retries'), str(e)))

        self.logger.error("Clamdscan failed after %s retries" %
                          self.config.getint(self.section, 'retries'))
        content = None
        return self._problemcode()
Example #29
0
    def examine(self, suspect):
        starttime = time.time()

        if suspect.size > self.config.getint(self.section, 'maxsize'):
            self._logger().info('Not scanning - message too big (message %s  bytes > config %s bytes )' %
                                (suspect.size, self.config.getint(self.section, 'maxsize')))
            return DUNNO

        content = suspect.get_message_rep().as_string()

        for i in range(0, self.config.getint(self.section, 'retries')):
            try:
                if self.config.getboolean(self.section, 'networkmode'):
                    viruses = self.scan_stream(content)
                else:
                    viruses = self.scan_file(suspect.tempfile)
                if viruses != None:
                    self._logger().info("Virus found in message from %s : %s" %
                                        (suspect.from_address, viruses))
                    suspect.tags['virus']['F-Prot'] = True
                    suspect.tags['FprotPlugin.virus'] = viruses
                    suspect.debug('Viruses found in message : %s' % viruses)
                else:
                    suspect.tags['virus']['F-Prot'] = False

                if viruses != None:
                    virusaction = self.config.get(self.section, 'virusaction')
                    actioncode = string_to_actioncode(virusaction, self.config)
                    firstinfected, firstvirusname = list(viruses.items())[0]
                    values = dict(
                        infectedfile=firstinfected, virusname=firstvirusname)
                    message = apply_template(
                        self.config.get(self.section, 'rejectmessage'), suspect, values)
                    return actioncode, message
                else:
                    return DUNNO
            except Exception as e:
                self._logger().warning("Error encountered while contacting fpscand (try %s of %s): %s" %
                                       (i + 1, self.config.getint(self.section, 'retries'), str(e)))
        self._logger().error("fpscand failed after %s retries" %
                             self.config.getint(self.section, 'retries'))
        content = None
        return self._problemcode()
Example #30
0
    def examine(self, suspect):
        actionrules = self.config.get(self.section, 'actionrules')
        if actionrules == None or actionrules == "":
            return DUNNO

        if not os.path.exists(actionrules):
            self.logger.error(
                'Action Rules file does not exist : %s' % actionrules)
            return DUNNO

        if self.filter == None:
            self.filter = SuspectFilter(actionrules)

        (match, arg) = self.filter.matches(suspect)
        if match:
            if arg == None or arg.strip() == '':
                self.logger.error("Rule match but no action defined.")
                return DUNNO

            arg = arg.strip()
            spl = arg.split(None, 1)
            actionstring = spl[0]
            message = None
            if len(spl) == 2:
                message = spl[1]
            self.logger.debug(
                "%s: Rule match! Action override: %s" % (suspect.id, arg.upper()))

            actioncode = string_to_actioncode(actionstring, self.config)
            if actioncode != None:
                return actioncode, message

            elif actionstring.upper() == 'REDIRECT':
                suspect.to_address = message.strip()
                suspect.recipients = [suspect.to_address, ]
                # todo: should we override to_domain? probably not
                # todo: check for invalid adress, multiple adressses
                # todo: document redirect action
            else:
                self.logger.error("Invalid action: %s" % arg)
                return DUNNO

        return DUNNO
Example #31
0
    def examine(self, suspect):
        if suspect.size > self.config.getint(self.section, 'maxsize'):
            self.logger.info(
                'Not scanning - message too big (message %s  bytes > config %s bytes )'
                % (suspect.size, self.config.getint(self.section, 'maxsize')))
            return DUNNO

        content = suspect.get_message_rep().as_string()

        for i in range(0, self.config.getint(self.section, 'retries')):
            try:
                viruses = self.scan_stream(content)
                if viruses != None:
                    self.logger.info("Virus found in message from %s : %s" %
                                     (suspect.from_address, viruses))
                    suspect.tags['virus']['drweb'] = True
                    suspect.tags['DrWebPlugin.virus'] = viruses
                    suspect.debug('Viruses found in message : %s' % viruses)
                else:
                    suspect.tags['virus']['drweb'] = False

                if viruses != None:
                    virusaction = self.config.get(self.section, 'virusaction')
                    actioncode = string_to_actioncode(virusaction, self.config)
                    firstinfected, firstvirusname = list(viruses.items())[0]
                    values = dict(infectedfile=firstinfected,
                                  virusname=firstvirusname)
                    message = apply_template(
                        self.config.get(self.section, 'rejectmessage'),
                        suspect, values)
                    return actioncode, message
                else:
                    return DUNNO
            except Exception as e:
                self.logger.warning(
                    "Error encountered while contacting drweb (try %s of %s): %s"
                    % (i + 1, self.config.getint(self.section,
                                                 'retries'), str(e)))
        self.logger.error("drweb scan failed after %s retries" %
                          self.config.getint(self.section, 'retries'))
        content = None
        return self._problemcode()
Example #32
0
 def _virusreport(self, suspect, viruses):
     actioncode = DUNNO
     message = None
     if viruses is not None:
         self.logger.info("%s Virus found in message from %s : %s" % (suspect.id, suspect.from_address, viruses))
         suspect.tags['virus']['ClamAV'] = True
         suspect.tags['ClamavPlugin.virus'] = viruses
         suspect.debug('viruses found in message : %s' % viruses)
     else:
         suspect.tags['virus']['ClamAV'] = False
 
     if viruses is not None:
         virusaction = self.config.get(self.section, 'virusaction')
         actioncode = string_to_actioncode(virusaction, self.config)
         firstinfected, firstvirusname = list(viruses.items())[0]
         values = dict(
             infectedfile=firstinfected, virusname=firstvirusname)
         message = apply_template(
             self.config.get(self.section, 'rejectmessage'), suspect, values)
     return actioncode, message
Example #33
0
 def examine(self, suspect):
     if not self.should_we_check_this_domain(suspect):
         return DUNNO
     envelope_recipient_domain = suspect.to_domain.lower()
     envelope_sender_domain = suspect.from_domain.lower()
     if envelope_sender_domain == envelope_recipient_domain:
         return DUNNO  # we only check the message if the env_sender_domain differs. If it's the same it will be caught by other means (like SPF)
     
     header_from_domains = []
     header_from_domain = extract_from_domain(suspect)
     if header_from_domain is None:
         self.logger.warn("%s: Could not extract header from domain for spearphish check" % suspect.id)
         return DUNNO
     else:
         header_from_domains.append(header_from_domain)
         self.logger.debug('%s: checking domain %s (source: From header address part)' % (suspect.id, header_from_domain))
     
     if self.config.getboolean(self.section, 'check_display_part'):
         display_from_domain = extract_from_domain(suspect, False)
         if display_from_domain is not None and display_from_domain not in header_from_domains:
             header_from_domains.append(display_from_domain)
             self.logger.debug('%s: checking domain %s (source: From header display part)' % (suspect.id, display_from_domain))
     
     actioncode = DUNNO
     message = None
     
     for header_from_domain in header_from_domains:
         if header_from_domain == envelope_recipient_domain:
             virusname = self.config.get(self.section, 'virusname')
             virusaction = self.config.get(self.section, 'virusaction')
             actioncode = string_to_actioncode(virusaction, self.config)
             
             logmsg = '%s: spear phish pattern detected, env_rcpt_domain=%s env_sender_domain=%s header_from_domain=%s' % \
                      (suspect.id, envelope_recipient_domain, envelope_sender_domain, header_from_domain)
             self.logger.info(logmsg)
             self.flag_as_phish(suspect, virusname)
             
             message = apply_template(self.config.get(self.section, 'rejectmessage'), suspect, {'virusname': virusname})
             break
     
     return actioncode, message
Example #34
0
    def examine(self, suspect):
        if suspect.size > self.config.getint(self.section, 'maxsize'):
            self._logger().info(
                'Not scanning - message too big (message %s  bytes > config %s bytes )'
                % (suspect.size, self.config.getint(self.section, 'maxsize')))
            return DUNNO

        try:
            viruses = self.scan_file(suspect.tempfile)

            if viruses is not None:
                self._logger().info("Virus found in message from %s : %s" %
                                    (suspect.from_address, viruses))
                suspect.tags['virus'][self.config.get(self.section,
                                                      'identifier')] = True
                suspect.tags['%s.virus' % self.config.get(
                    self.section, 'identifier')] = viruses
                suspect.debug('Viruses found in message : %s' % viruses)
            else:
                suspect.tags['virus'][self.config.get(self.section,
                                                      'identifier')] = False

            if viruses is not None:
                virusaction = self.config.get(self.section, 'virusaction')
                actioncode = string_to_actioncode(virusaction, self.config)
                firstinfected, firstvirusname = viruses.items()[0]

                values = dict(infectedfile=firstinfected,
                              virusname=firstvirusname)

                message = apply_template(
                    self.config.get(self.section, 'rejectmessage'), suspect,
                    values)
                return actioncode, message
            else:
                return DUNNO
        except Exception as e:
            self._logger().warning(
                "Error encountered while running cmdline av scan: %s" % str(e))

        return self._problemcode()
Example #35
0
    def _virusreport(self, suspect, viruses):
        actioncode = DUNNO
        message = None
        if viruses is not None:
            self.logger.info("%s Virus found in message from %s : %s" %
                             (suspect.id, suspect.from_address, viruses))
            suspect.tags['virus']['ClamAV'] = True
            suspect.tags['ClamavPlugin.virus'] = viruses
            suspect.debug('viruses found in message : %s' % viruses)
        else:
            suspect.tags['virus']['ClamAV'] = False

        if viruses is not None:
            virusaction = self.config.get(self.section, 'virusaction')
            actioncode = string_to_actioncode(virusaction, self.config)
            firstinfected, firstvirusname = list(viruses.items())[0]
            values = dict(infectedfile=firstinfected, virusname=firstvirusname)
            message = apply_template(
                self.config.get(self.section, 'rejectmessage'), suspect,
                values)
        return actioncode, message
    def examine(self,suspect):
        if self.rbllookup==None:
            self.rbllookup=domainmagic.rbl.RBLLookup()
            self.rbllookup.from_config(self.config.get(self.section,'blacklistconfig'))
        if self.tldmagic==None:
            self.tldmagic=domainmagic.tld.TLDMagic()
            
        

        urls=suspect.get_tag('body.uris',defaultvalue=[])
        #self.logger.info("Body URIs to check: %s"%urls)
        domains=set(map(domainmagic.extractor.domain_from_uri,urls))
        
        counter=0
        for domain in domains:
            counter+=1
            if counter>self.config.getint(self.section,'maxdomains'):
                    self.logger.info("maximum number of domains reached")
                    break
            
            tldcount=self.tldmagic.get_tld_count(domain)
            parts=domain.split('.')
            
            if self.config.getboolean(self.section,'checksubdomains'):
                subrange=range(tldcount+1,len(parts)+1)
            else:
                subrange=[tldcount+1]
            
            for subindex in subrange:
                subdomain='.'.join(parts[-subindex:])

                listings=self.rbllookup.listings(subdomain)
                for identifier,humanreadable in listings.iteritems():
                    self.logger.info("%s : url host %s flagged as %s because %s"%(suspect.id,domain,identifier,humanreadable))
                    return string_to_actioncode(self.config.get(self.section,'action'), self.config),apply_template(self.config.get(self.section,'message'), suspect, dict(domain=domain,blacklist=identifier))
    
        return DUNNO
Example #37
0
 def examine(self, suspect):
     if suspect.get_tag('send_lmtp') is not True:
         return DUNNO
     
     lmtpfrom = self.config.get(self.section, 'lmtpfrom')
     if not lmtpfrom:
         lmtpfrom = suspect.from_address
     
     if self.config.getboolean(self.section, 'sendoriginal'):
         content = suspect.get_original_source()
     else:
         content = suspect.get_source()
         
     try:
         lmtp = self.__init_socket()
     except Exception as e:
         self.logger.error('%s could not connect to LMTP server: %s' % (suspect.id, str(e)))
         return DEFER, 'could not connect to LMTP server'
     
     try:
         self.__auth(lmtp)
     except Exception as e:
         self.logger.error('%s could not authenticate to LMTP server: %s' % (suspect.id, str(e)))
         return DEFER, 'could not authenticate to LMTP server'
     
     try:
         errors = lmtp.sendmail(lmtpfrom, suspect.to_address, content)
         for rcpt in errors:
             self.logger.error('%s could not deliver to LMTP server for %s: %s' % (suspect.id, rcpt, errors[rcpt]))
     except Exception as e:
         self.logger.error('%s could not deliver to LMTP server: %s' % (suspect.id, str(e)))
         return DEFER, 'could not deliver to LMTP server'
     finally:
         lmtp.quit()
         
     successaction = string_to_actioncode(self.config.get(self.section, 'successaction'))
     return successaction
Example #38
0
    def examine(self, suspect):
        enginename = "sophos"

        if suspect.size > self.config.getint(self.section, "maxsize"):
            self.logger.info("Not scanning - message too big")
            return

        content = suspect.get_source()

        for i in range(0, self.config.getint(self.section, "retries")):
            try:
                viruses = self.scan_stream(content)
                if viruses != None:
                    self.logger.info("Virus found in message from %s : %s" % (suspect.from_address, viruses))
                    suspect.tags["virus"][enginename] = True
                    suspect.tags["%s.virus" % enginename] = viruses
                    suspect.debug("viruses found in message : %s" % viruses)
                else:
                    suspect.tags["virus"][enginename] = False

                if viruses != None:
                    virusaction = self.config.get(self.section, "virusaction")
                    actioncode = string_to_actioncode(virusaction, self.config)
                    firstinfected, firstvirusname = viruses.items()[0]
                    values = dict(infectedfile=firstinfected, virusname=firstvirusname)
                    message = apply_template(self.config.get(self.section, "rejectmessage"), suspect, values)
                    return actioncode, message
                return DUNNO
            except Exception as e:
                self.logger.warning(
                    "Error encountered while contacting SSSP server (try %s of %s): %s"
                    % (i + 1, self.config.getint(self.section, "retries"), str(e))
                )
        self.logger.error("SSSP scan failed after %s retries" % self.config.getint(self.section, "retries"))
        content = None
        return self._problemcode()
Example #39
0
    def walk(self, suspect):
        """walks through a message and checks each attachment according to the rulefile specified in the config"""

        blockaction = self.config.get(self.section, 'blockaction')
        blockactioncode = string_to_actioncode(blockaction)

        # try db rules first
        self.rulescache.reloadifnecessary()
        dbconn = ''
        if self.config.has_option(self.section, 'dbconnectstring'):
            dbconn = self.config.get(self.section, 'dbconnectstring')

        if dbconn.strip() != '':
            self.logger.debug('Loading attachment rules from database')
            query = self.config.get(self.section, 'query')
            dbfile = DBFile(dbconn, query)
            user_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_address, 'checktype': FUATT_CHECKTYPE_FN}))
            user_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_address, 'checktype': FUATT_CHECKTYPE_CT}))
            user_archive_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_address, 'checktype': FUATT_CHECKTYPE_ARCHIVE_FN}))
            user_archive_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_address, 'checktype': FUATT_CHECKTYPE_ARCHIVE_CT}))
            self.logger.debug('Found %s filename rules, %s content-type rules, %s archive filename rules, %s archive content rules for address %s' %
                              (len(user_names), len(user_ctypes), len(user_archive_names), len(user_archive_ctypes), suspect.to_address))

            domain_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_domain, 'checktype': FUATT_CHECKTYPE_FN}))
            domain_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_domain, 'checktype': FUATT_CHECKTYPE_CT}))
            domain_archive_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_domain, 'checktype': FUATT_CHECKTYPE_ARCHIVE_FN}))
            domain_archive_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_domain, 'checktype': FUATT_CHECKTYPE_ARCHIVE_CT}))
            self.logger.debug('Found %s filename rules, %s content-type rules, %s archive filename rules, %s archive content rules for domain %s' %
                              (len(domain_names), len(domain_ctypes), len(domain_archive_names), len(domain_archive_ctypes), suspect.to_domain))
        else:
            self.logger.debug('Loading attachment rules from filesystem')
            user_names = self.rulescache.getNAMERules(suspect.to_address)
            user_ctypes = self.rulescache.getCTYPERules(suspect.to_address)
            user_archive_names = self.rulescache.getARCHIVENAMERules(
                suspect.to_address)
            user_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
                suspect.to_address)

            domain_names = self.rulescache.getNAMERules(suspect.to_domain)
            domain_ctypes = self.rulescache.getCTYPERules(suspect.to_domain)
            domain_archive_names = self.rulescache.getARCHIVENAMERules(
                suspect.to_domain)
            domain_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
                suspect.to_domain)

        # always get defaults from file
        default_names = self.rulescache.getNAMERules(FUATT_DEFAULT)
        default_ctypes = self.rulescache.getCTYPERules(FUATT_DEFAULT)
        default_archive_names = self.rulescache.getARCHIVENAMERules(
            FUATT_DEFAULT)
        default_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
            FUATT_DEFAULT)

        m = suspect.get_message_rep()
        for i in m.walk():
            if i.is_multipart():
                continue
            contenttype_mime = i.get_content_type()
            att_name = i.get_filename(None)

            if not att_name:
                # workaround for mimetypes, it always takes .ksh for text/plain
                if i.get_content_type() == 'text/plain':
                    ext = '.txt'
                else:
                    ext = mimetypes.guess_extension(i.get_content_type())

                if ext == None:
                    ext = ''
                att_name = 'unnamed%s' % ext

            res = self.matchMultipleSets(
                [user_names, domain_names, default_names], att_name, suspect, att_name)
            if res == ATTACHMENT_SILENTDELETE:
                self._debuginfo(
                    suspect, "Attachment name=%s SILENT DELETE : blocked by name" % att_name)
                return DELETE
            if res == ATTACHMENT_BLOCK:
                self._debuginfo(
                    suspect, "Attachment name=%s : blocked by name)" % att_name)
                message = suspect.tags['FiletypePlugin.errormessage']
                return blockactioncode, message

            # go through content type rules
            res = self.matchMultipleSets(
                [user_ctypes, domain_ctypes, default_ctypes], contenttype_mime, suspect, att_name)
            if res == ATTACHMENT_SILENTDELETE:
                self._debuginfo(
                    suspect, "Attachment name=%s content-type=%s SILENT DELETE: blocked by mime content type (message source)" % (att_name, contenttype_mime))
                return DELETE
            if res == ATTACHMENT_BLOCK:
                self._debuginfo(
                    suspect, "Attachment name=%s content-type=%s : blocked by mime content type (message source)" % (att_name, contenttype_mime))
                message = suspect.tags['FiletypePlugin.errormessage']
                return blockactioncode, message

            if MAGIC_AVAILABLE:
                pl = i.get_payload(decode=True)
                contenttype_magic = self.getBuffertype(pl)
                res = self.matchMultipleSets(
                    [user_ctypes, domain_ctypes, default_ctypes], contenttype_magic, suspect, att_name)
                if res == ATTACHMENT_SILENTDELETE:
                    self._debuginfo(
                        suspect, "Attachment name=%s content-type=%s SILENT DELETE: blocked by mime content type (magic)" % (att_name, contenttype_mime))
                    return DELETE
                if res == ATTACHMENT_BLOCK:
                    self._debuginfo(
                        suspect, "Attachment name=%s content-type=%s : blocked by mime content type (magic)" % (att_name, contenttype_mime))
                    message = suspect.tags['FiletypePlugin.errormessage']
                    return blockactioncode, message

            # archives
            if self.config.getboolean(self.section, 'checkarchivenames') or self.config.getboolean(self.section, 'checkarchivecontent'):
                archive_type=None
                for arext in self.supported_archive_extensions:
                    if att_name.lower().endswith('.%s'%arext):
                        archive_type=arext
                        break

                if archive_type!=None:
                    try:
                        pl = StringIO(i.get_payload(decode=True))
                        archive_handle = self._archive_handle(archive_type,pl)
                        namelist = self._archive_namelist(archive_type,archive_handle)
                        if self.config.getboolean(self.section, 'checkarchivenames'):
                            for name in namelist:
                                if type(name)==unicode: #rarfile returns unicode objects which mess up generated bounces
                                    name=name.encode("utf-8","ignore")
                                res = self.matchMultipleSets(
                                    [user_archive_names, domain_archive_names, default_archive_names], name, suspect, name)
                                if res == ATTACHMENT_SILENTDELETE:
                                    self._debuginfo(
                                        suspect, "Blocked filename in archive %s SILENT DELETE" % att_name)
                                    return DELETE
                                if res == ATTACHMENT_BLOCK:
                                    self._debuginfo(
                                        suspect, "Blocked filename in archive %s" % att_name)
                                    message = suspect.tags[
                                        'FiletypePlugin.errormessage']
                                    return blockactioncode, message

                        if MAGIC_AVAILABLE and self.config.getboolean(self.section, 'checkarchivecontent'):
                            for name in namelist:
                                safename = self.asciionly(name)
                                extracted = self._archive_extract(archive_type,archive_handle,name)
                                if extracted==None:
                                    self._debuginfo(suspect,'%s not extracted - too large'%(safename))
                                contenttype_magic = self.getBuffertype(
                                    extracted)
                                res = self.matchMultipleSets(
                                    [user_archive_ctypes, domain_archive_ctypes, default_archive_ctypes], contenttype_magic, suspect, name)
                                if res == ATTACHMENT_SILENTDELETE:
                                    self._debuginfo(
                                        suspect, "Extracted file %s from archive %s content-type=%s SILENT DELETE: blocked by mime content type (magic)" % (safename, att_name, contenttype_magic))
                                    return DELETE
                                if res == ATTACHMENT_BLOCK:
                                    self._debuginfo(
                                        suspect, "Extracted file %s from archive %s content-type=%s : blocked by mime content type (magic)" % (safename, att_name, contenttype_magic))
                                    message = suspect.tags[
                                        'FiletypePlugin.errormessage']
                                    return blockactioncode, message

                    except Exception, e:
                        self.logger.warning(
                            "archive scanning failed in attachment %s: %s" % (att_name, str(e)))
Example #40
0
 def lint(self):
     viract = self.config.get(self.section, 'virusaction')
     print "Virusaction: %s" % actioncode_to_string(
         string_to_actioncode(viract, self.config))
     allok = (self.checkConfig() and self.lint_eicar())
     return allok
Example #41
0
    def walk(self, suspect):
        """walks through a message and checks each attachment according to the rulefile specified in the config"""

        blockaction = self.config.get(self.section, 'blockaction')
        blockactioncode = string_to_actioncode(blockaction)

        # try db rules first
        self.rulescache.reloadifnecessary()
        dbconn = ''
        if self.config.has_option(self.section, 'dbconnectstring'):
            dbconn = self.config.get(self.section, 'dbconnectstring')

        if dbconn.strip() != '':
            self.logger.debug('Loading attachment rules from database')
            query = self.config.get(self.section, 'query')
            dbfile = DBFile(dbconn, query)
            user_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_address, 'checktype': FUATT_CHECKTYPE_FN}))
            user_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_address, 'checktype': FUATT_CHECKTYPE_CT}))
            user_archive_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_address, 'checktype': FUATT_CHECKTYPE_ARCHIVE_FN}))
            user_archive_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_address, 'checktype': FUATT_CHECKTYPE_ARCHIVE_CT}))
            self.logger.debug('Found %s filename rules, %s content-type rules, %s archive filename rules, %s archive content rules for address %s' %
                              (len(user_names), len(user_ctypes), len(user_archive_names), len(user_archive_ctypes), suspect.to_address))

            domain_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_domain, 'checktype': FUATT_CHECKTYPE_FN}))
            domain_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_domain, 'checktype': FUATT_CHECKTYPE_CT}))
            domain_archive_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_domain, 'checktype': FUATT_CHECKTYPE_ARCHIVE_FN}))
            domain_archive_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_domain, 'checktype': FUATT_CHECKTYPE_ARCHIVE_CT}))
            self.logger.debug('Found %s filename rules, %s content-type rules, %s archive filename rules, %s archive content rules for domain %s' %
                              (len(domain_names), len(domain_ctypes), len(domain_archive_names), len(domain_archive_ctypes), suspect.to_domain))
        else:
            self.logger.debug('Loading attachment rules from filesystem dir %s'%(self.config.get(self.section,'rulesdir')))
            user_names = self.rulescache.getNAMERules(suspect.to_address)
            user_ctypes = self.rulescache.getCTYPERules(suspect.to_address)
            user_archive_names = self.rulescache.getARCHIVENAMERules(
                suspect.to_address)
            user_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
                suspect.to_address)

            domain_names = self.rulescache.getNAMERules(suspect.to_domain)
            domain_ctypes = self.rulescache.getCTYPERules(suspect.to_domain)
            domain_archive_names = self.rulescache.getARCHIVENAMERules(
                suspect.to_domain)
            domain_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
                suspect.to_domain)

        # always get defaults from file
        default_names = self.rulescache.getNAMERules(FUATT_DEFAULT)
        default_ctypes = self.rulescache.getCTYPERules(FUATT_DEFAULT)
        default_archive_names = self.rulescache.getARCHIVENAMERules(
            FUATT_DEFAULT)
        default_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
            FUATT_DEFAULT)

        m = suspect.get_message_rep()
        for part in self.walk_all_parts(m):
            if part.is_multipart():
                continue
            contenttype_mime = part.get_content_type()
            att_name = part.get_filename(None)

            if att_name:
                # some filenames are encoded, try to decode
                try:
                    att_name = ''.join([x[0] for x in decode_header(att_name)])
                except Exception:
                    pass
            else:
                # workaround for mimetypes, it always takes .ksh for text/plain
                if part.get_content_type() == 'text/plain':
                    ext = '.txt'
                else:
                    ext = mimetypes.guess_extension(part.get_content_type())

                if ext is None:
                    ext = ''
                att_name = 'unnamed%s' % ext

            att_name = self.asciionly(att_name)

            res = self.matchMultipleSets(
                [user_names, domain_names, default_names], att_name, suspect, att_name)
            if res == ATTACHMENT_SILENTDELETE:
                self._debuginfo(
                    suspect, "Attachment name=%s SILENT DELETE : blocked by name" % att_name)
                return DELETE
            if res == ATTACHMENT_BLOCK:
                self._debuginfo(
                    suspect, "Attachment name=%s : blocked by name)" % att_name)
                message = suspect.tags['FiletypePlugin.errormessage']
                return blockactioncode, message

            # go through content type rules
            res = self.matchMultipleSets(
                [user_ctypes, domain_ctypes, default_ctypes], contenttype_mime, suspect, att_name)
            if res == ATTACHMENT_SILENTDELETE:
                self._debuginfo(
                    suspect, "Attachment name=%s content-type=%s SILENT DELETE: blocked by mime content type (message source)" % (att_name, contenttype_mime))
                return DELETE
            if res == ATTACHMENT_BLOCK:
                self._debuginfo(
                    suspect, "Attachment name=%s content-type=%s : blocked by mime content type (message source)" % (att_name, contenttype_mime))
                message = suspect.tags['FiletypePlugin.errormessage']
                return blockactioncode, message

            contenttype_magic = None
            if MAGIC_AVAILABLE:
                pl = part.get_payload(decode=True)
                contenttype_magic = self.getBuffertype(pl)
                res = self.matchMultipleSets(
                    [user_ctypes, domain_ctypes, default_ctypes], contenttype_magic, suspect, att_name)
                if res == ATTACHMENT_SILENTDELETE:
                    self._debuginfo(
                        suspect, "Attachment name=%s content-type=%s SILENT DELETE: blocked by mime content type (magic)" % (att_name, contenttype_mime))
                    return DELETE
                if res == ATTACHMENT_BLOCK:
                    self._debuginfo(
                        suspect, "Attachment name=%s content-type=%s : blocked by mime content type (magic)" % (att_name, contenttype_mime))
                    message = suspect.tags['FiletypePlugin.errormessage']
                    return blockactioncode, message

            # archives
            if self.checkarchivenames or self.checkarchivecontent:

                # try guessing the archive type based on magic content type first
                # we don't need to check for MAGIC_AVAILABLE here, if it is available the contenttype_magic is not None
                archive_type = self.archive_type_from_content_type(contenttype_magic)

                # if it didn't work, try to guess by the filename extension, if it is enabled
                if archive_type is None:
                    # sort by length, so tar.gz is checked before .gz
                    for arext in sorted(self.supported_archive_extensions.keys(), key=lambda x: len(x), reverse=True):
                        if att_name.lower().endswith('.%s' % arext):
                            archive_type = self.supported_archive_extensions[arext]
                            break
                if archive_type is not None:
                    self.logger.debug("Extracting {attname} as {artype}".format(attname=att_name,artype=archive_type))
                    try:
                        pl = BytesIO(part.get_payload(decode=True))
                        archive_handle = self._archive_handle(archive_type, pl)
                        namelist = self._archive_namelist(archive_type, archive_handle)
                        if self.checkarchivenames:
                            for name in namelist:
                                # rarfile returns unicode objects which mess up
                                # generated bounces
                                if sys.version_info[0] == 2:
                                    # Py3 defaults to unicode
                                    name = self.asciionly(name)
                                res = self.matchMultipleSets(
                                    [user_archive_names, domain_archive_names, default_archive_names], name, suspect, name)
                                if res == ATTACHMENT_SILENTDELETE:
                                    self._debuginfo(
                                        suspect, "Blocked filename in archive %s SILENT DELETE" % att_name)
                                    return DELETE
                                if res == ATTACHMENT_BLOCK:
                                    self._debuginfo(
                                        suspect, "Blocked filename in archive %s" % att_name)
                                    message = suspect.tags['FiletypePlugin.errormessage']
                                    return blockactioncode, message

                        if MAGIC_AVAILABLE and self.checkarchivecontent:
                            for name in namelist:
                                safename = self.asciionly(name)
                                extracted = self._archive_extract(archive_type, archive_handle, name)
                                if extracted is None:
                                    self._debuginfo(
                                        suspect, '%s not extracted - too large' % (safename))
                                contenttype_magic = self.getBuffertype(
                                    extracted)
                                res = self.matchMultipleSets(
                                    [user_archive_ctypes, domain_archive_ctypes, default_archive_ctypes], contenttype_magic, suspect, name)
                                if res == ATTACHMENT_SILENTDELETE:
                                    self._debuginfo(
                                        suspect, "Extracted file %s from archive %s content-type=%s SILENT DELETE: blocked by mime content type (magic)" % (safename, att_name, contenttype_magic))
                                    return DELETE
                                if res == ATTACHMENT_BLOCK:
                                    self._debuginfo(
                                        suspect, "Extracted file %s from archive %s content-type=%s : blocked by mime content type (magic)" % (safename, att_name, contenttype_magic))
                                    message = suspect.tags['FiletypePlugin.errormessage']
                                    return blockactioncode, message
                        
                        if hasattr(archive_handle, 'close'):
                            archive_handle.close()
                        pl.close()
                        
                    except Exception:
                        self.logger.warning(
                            "archive scanning failed in attachment {attname}: {error}".format(attname=att_name, error=traceback.format_exc() ))
        return DUNNO
Example #42
0
    def examine(self, suspect):
        # check if someone wants to skip sa checks
        if suspect.get_tag('SAPlugin.skip') is True:
            self.logger.debug(
                '%s Skipping SA Plugin (requested by previous plugin)' %
                suspect.id)
            suspect.set_tag('SAPlugin.skipreason',
                            'requested by previous plugin')
            return DUNNO

        runtimeconfig = DBConfig(self.config, suspect)

        spamsize = suspect.size
        maxsize = self.config.getint(self.section, 'maxsize')
        spamheadername = self.config.get(self.section, 'spamheader')

        if spamsize > maxsize:
            self.logger.info('%s Size Skip, %s > %s' %
                             (suspect.id, spamsize, maxsize))
            suspect.debug('Too big for spamchecks. %s > %s' %
                          (spamsize, maxsize))
            prependheader = self.config.get('main', 'prependaddedheaders')
            suspect.addheader(
                "%sSA-SKIP" % prependheader,
                'Too big for spamchecks. %s > %s' % (spamsize, maxsize))
            suspect.set_tag('SAPlugin.skipreason', 'size skip')
            return self.check_sql_blacklist(suspect)

        forwardoriginal = self.config.getboolean(self.section,
                                                 'forwardoriginal')

        if self.config.getboolean(self.section, 'scanoriginal'):
            spam = suspect.get_original_source()
        else:
            spam = suspect.get_source()

        # prepend temporary headers set by other plugins
        tempheader = suspect.get_tag('SAPlugin.tempheader')
        if tempheader is not None:
            if type(tempheader) == list:
                tempheader = "\r\n".join(tempheader)
            tempheader = tempheader.strip()
            if tempheader != '':
                spam = tempheader + '\r\n' + spam

        if forwardoriginal:
            ret = self.safilter_report(spam, suspect.to_address)
            if ret is None:
                suspect.debug('SA report Scan failed - please check error log')
                self.logger.error('%s SA report scan FAILED' % suspect.id)
                suspect.addheader(
                    '%sSA-SKIP' %
                    self.config.get('main', 'prependaddedheaders'),
                    'SA scan failed')
                suspect.set_tag('SAPlugin.skipreason', 'scan failed')
                return self._problemcode()
            isspam, spamscore, report = ret
            suspect.tags['SAPlugin.report'] = report

        else:
            filtered = self.safilter(spam, suspect.to_address)
            if filtered is None:
                suspect.debug('SA Scan failed - please check error log')
                self.logger.error('%s SA scan FAILED' % suspect.id)
                suspect.addheader(
                    '%sSA-SKIP' %
                    self.config.get('main', 'prependaddedheaders'),
                    'SA scan failed')
                suspect.set_tag('SAPlugin.skipreason', 'scan failed')
                return self._problemcode()
            else:
                content = filtered

            newmsgrep = email.message_from_string(content)
            suspect.set_source(content)
            isspam, spamscore = self._extract_spamstatus(
                newmsgrep, spamheadername, suspect)

        action = DUNNO
        message = None

        if isspam:
            self.logger.debug('%s Message is spam' % suspect.id)
            suspect.debug('Message is spam')

            configaction = string_to_actioncode(
                runtimeconfig.get(self.section, 'lowspamaction'), self.config)
            if configaction is not None:
                action = configaction
            values = dict(spamscore=spamscore)
            message = apply_template(
                self.config.get(self.section, 'rejectmessage'), suspect,
                values)
        else:
            self.logger.debug('%s Message is not spam' % suspect.id)
            suspect.debug('Message is not spam')

        suspect.tags['spam']['SpamAssassin'] = isspam
        suspect.tags['highspam']['SpamAssassin'] = False
        if spamscore is not None:
            suspect.tags['SAPlugin.spamscore'] = spamscore
            highspamlevel = runtimeconfig.getfloat(self.section,
                                                   'highspamlevel')
            if spamscore >= highspamlevel:
                suspect.tags['highspam']['SpamAssassin'] = True
                configaction = string_to_actioncode(
                    runtimeconfig.get(self.section, 'highspamaction'),
                    self.config)
                if configaction is not None:
                    action = configaction
        return action, message
Example #43
0
    def walk(self, suspect):
        """walks through a message and checks each attachment according to the rulefile specified in the config"""

        blockaction = self.config.get(self.section, 'blockaction')
        blockactioncode = string_to_actioncode(blockaction)

        # try db rules first
        self.rulescache.reloadifnecessary()
        dbconn = ''
        if self.config.has_option(self.section, 'dbconnectstring'):
            dbconn = self.config.get(self.section, 'dbconnectstring')

        if dbconn.strip() != '':
            self.logger.debug('Loading attachment rules from database')
            query = self.config.get(self.section, 'query')
            dbfile = DBFile(dbconn, query)
            user_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_address,
                    'checktype': FUATT_CHECKTYPE_FN
                }))
            user_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_address,
                    'checktype': FUATT_CHECKTYPE_CT
                }))
            user_archive_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_address,
                    'checktype': FUATT_CHECKTYPE_ARCHIVE_FN
                }))
            user_archive_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_address,
                    'checktype': FUATT_CHECKTYPE_ARCHIVE_CT
                }))
            self.logger.debug(
                'Found %s filename rules, %s content-type rules, %s archive filename rules, %s archive content rules for address %s'
                % (len(user_names), len(user_ctypes), len(user_archive_names),
                   len(user_archive_ctypes), suspect.to_address))

            domain_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_domain,
                    'checktype': FUATT_CHECKTYPE_FN
                }))
            domain_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_domain,
                    'checktype': FUATT_CHECKTYPE_CT
                }))
            domain_archive_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_domain,
                    'checktype': FUATT_CHECKTYPE_ARCHIVE_FN
                }))
            domain_archive_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_domain,
                    'checktype': FUATT_CHECKTYPE_ARCHIVE_CT
                }))
            self.logger.debug(
                'Found %s filename rules, %s content-type rules, %s archive filename rules, %s archive content rules for domain %s'
                % (len(domain_names), len(domain_ctypes),
                   len(domain_archive_names), len(domain_archive_ctypes),
                   suspect.to_domain))
        else:
            self.logger.debug(
                'Loading attachment rules from filesystem dir %s' %
                (self.config.get(self.section, 'rulesdir')))
            user_names = self.rulescache.getNAMERules(suspect.to_address)
            user_ctypes = self.rulescache.getCTYPERules(suspect.to_address)
            user_archive_names = self.rulescache.getARCHIVENAMERules(
                suspect.to_address)
            user_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
                suspect.to_address)

            domain_names = self.rulescache.getNAMERules(suspect.to_domain)
            domain_ctypes = self.rulescache.getCTYPERules(suspect.to_domain)
            domain_archive_names = self.rulescache.getARCHIVENAMERules(
                suspect.to_domain)
            domain_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
                suspect.to_domain)

        # always get defaults from file
        default_names = self.rulescache.getNAMERules(FUATT_DEFAULT)
        default_ctypes = self.rulescache.getCTYPERules(FUATT_DEFAULT)
        default_archive_names = self.rulescache.getARCHIVENAMERules(
            FUATT_DEFAULT)
        default_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
            FUATT_DEFAULT)

        m = suspect.get_message_rep()
        for part in self.walk_all_parts(m):
            if part.is_multipart():
                continue
            contenttype_mime = part.get_content_type()
            att_name = part.get_filename(None)

            if att_name:
                # some filenames are encoded, try to decode
                try:
                    att_name = ''.join([x[0] for x in decode_header(att_name)])
                except Exception:
                    pass
            else:
                # workaround for mimetypes, it always takes .ksh for text/plain
                if part.get_content_type() == 'text/plain':
                    ext = '.txt'
                else:
                    ext = mimetypes.guess_extension(part.get_content_type())

                if ext is None:
                    ext = ''
                att_name = 'unnamed%s' % ext

            att_name = self.asciionly(att_name)

            res = self.matchMultipleSets(
                [user_names, domain_names, default_names], att_name, suspect,
                att_name)
            if res == ATTACHMENT_SILENTDELETE:
                self._debuginfo(
                    suspect,
                    "Attachment name=%s SILENT DELETE : blocked by name" %
                    att_name)
                return DELETE
            if res == ATTACHMENT_BLOCK:
                self._debuginfo(
                    suspect,
                    "Attachment name=%s : blocked by name)" % att_name)
                message = suspect.tags['FiletypePlugin.errormessage']
                return blockactioncode, message

            # go through content type rules
            res = self.matchMultipleSets(
                [user_ctypes, domain_ctypes, default_ctypes], contenttype_mime,
                suspect, att_name)
            if res == ATTACHMENT_SILENTDELETE:
                self._debuginfo(
                    suspect,
                    "Attachment name=%s content-type=%s SILENT DELETE: blocked by mime content type (message source)"
                    % (att_name, contenttype_mime))
                return DELETE
            if res == ATTACHMENT_BLOCK:
                self._debuginfo(
                    suspect,
                    "Attachment name=%s content-type=%s : blocked by mime content type (message source)"
                    % (att_name, contenttype_mime))
                message = suspect.tags['FiletypePlugin.errormessage']
                return blockactioncode, message

            contenttype_magic = None
            if MAGIC_AVAILABLE:
                pl = part.get_payload(decode=True)
                contenttype_magic = self.getBuffertype(pl)
                res = self.matchMultipleSets(
                    [user_ctypes, domain_ctypes, default_ctypes],
                    contenttype_magic, suspect, att_name)
                if res == ATTACHMENT_SILENTDELETE:
                    self._debuginfo(
                        suspect,
                        "Attachment name=%s content-type=%s SILENT DELETE: blocked by mime content type (magic)"
                        % (att_name, contenttype_mime))
                    return DELETE
                if res == ATTACHMENT_BLOCK:
                    self._debuginfo(
                        suspect,
                        "Attachment name=%s content-type=%s : blocked by mime content type (magic)"
                        % (att_name, contenttype_mime))
                    message = suspect.tags['FiletypePlugin.errormessage']
                    return blockactioncode, message

            # archives
            if self.checkarchivenames or self.checkarchivecontent:

                # try guessing the archive type based on magic content type first
                # we don't need to check for MAGIC_AVAILABLE here, if it is available the contenttype_magic is not None
                archive_type = self.archive_type_from_content_type(
                    contenttype_magic)

                # if it didn't work, try to guess by the filename extension, if it is enabled
                if archive_type is None:
                    # sort by length, so tar.gz is checked before .gz
                    for arext in sorted(
                            self.supported_archive_extensions.keys(),
                            key=lambda x: len(x),
                            reverse=True):
                        if att_name.lower().endswith('.%s' % arext):
                            archive_type = self.supported_archive_extensions[
                                arext]
                            break
                if archive_type is not None:
                    self.logger.debug(
                        "Extracting {attname} as {artype}".format(
                            attname=att_name, artype=archive_type))
                    try:
                        pl = BytesIO(part.get_payload(decode=True))
                        archive_handle = self._archive_handle(archive_type, pl)
                        namelist = self._archive_namelist(
                            archive_type, archive_handle)
                        if self.checkarchivenames:
                            for name in namelist:
                                # rarfile returns unicode objects which mess up
                                # generated bounces
                                if sys.version_info[0] == 2:
                                    # Py3 defaults to unicode
                                    name = self.asciionly(name)
                                res = self.matchMultipleSets([
                                    user_archive_names, domain_archive_names,
                                    default_archive_names
                                ], name, suspect, name)
                                if res == ATTACHMENT_SILENTDELETE:
                                    self._debuginfo(
                                        suspect,
                                        "Blocked filename in archive %s SILENT DELETE"
                                        % att_name)
                                    return DELETE
                                if res == ATTACHMENT_BLOCK:
                                    self._debuginfo(
                                        suspect,
                                        "Blocked filename in archive %s" %
                                        att_name)
                                    message = suspect.tags[
                                        'FiletypePlugin.errormessage']
                                    return blockactioncode, message

                        if MAGIC_AVAILABLE and self.checkarchivecontent:
                            for name in namelist:
                                safename = self.asciionly(name)
                                extracted = self._archive_extract(
                                    archive_type, archive_handle, name)
                                if extracted is None:
                                    self._debuginfo(
                                        suspect,
                                        '%s not extracted - too large' %
                                        (safename))
                                contenttype_magic = self.getBuffertype(
                                    extracted)
                                res = self.matchMultipleSets([
                                    user_archive_ctypes, domain_archive_ctypes,
                                    default_archive_ctypes
                                ], contenttype_magic, suspect, name)
                                if res == ATTACHMENT_SILENTDELETE:
                                    self._debuginfo(
                                        suspect,
                                        "Extracted file %s from archive %s content-type=%s SILENT DELETE: blocked by mime content type (magic)"
                                        % (safename, att_name,
                                           contenttype_magic))
                                    return DELETE
                                if res == ATTACHMENT_BLOCK:
                                    self._debuginfo(
                                        suspect,
                                        "Extracted file %s from archive %s content-type=%s : blocked by mime content type (magic)"
                                        % (safename, att_name,
                                           contenttype_magic))
                                    message = suspect.tags[
                                        'FiletypePlugin.errormessage']
                                    return blockactioncode, message

                        if hasattr(archive_handle, 'close'):
                            archive_handle.close()
                        pl.close()

                    except Exception:
                        self.logger.warning(
                            "archive scanning failed in attachment {attname}: {error}"
                            .format(attname=att_name,
                                    error=traceback.format_exc()))
        return DUNNO
Example #44
0
    def walk(self,suspect):
        """walks through a message and checks each attachment according to the rulefile specified in the config"""
        
        blockaction=self.config.get(self.section,'blockaction')
        blockactioncode=string_to_actioncode(blockaction)
        
        #try db rules first
        self.rulescache.reloadifnecessary()
        dbconn=''
        if self.config.has_option(self.section,'dbconnectstring'):
            dbconn=self.config.get(self.section,'dbconnectstring')
           
        if dbconn.strip()!='':
            self.logger.debug('Loading attachment rules from database')
            query=self.config.get(self.section,'query')
            dbfile=DBFile(dbconn, query)
            user_names=self.rulescache.get_rules_from_config_lines(dbfile.getContent({'scope':suspect.to_address,'checktype':FUATT_CHECKTYPE_FN}))
            user_ctypes=self.rulescache.get_rules_from_config_lines(dbfile.getContent({'scope':suspect.to_address,'checktype':FUATT_CHECKTYPE_CT}))
            self.logger.debug('Found %s filename rules, %s content-type rules for address %s'%(len(user_names),len(user_ctypes),suspect.to_address))
            domain_names=self.rulescache.get_rules_from_config_lines(dbfile.getContent({'scope':suspect.to_domain,'checktype':FUATT_CHECKTYPE_FN}))
            domain_ctypes=self.rulescache.get_rules_from_config_lines(dbfile.getContent({'scope':suspect.to_domain,'checktype':FUATT_CHECKTYPE_CT}))
            self.logger.debug('Found %s filename rules, %s content-type rules for domain %s'%(len(domain_names),len(domain_ctypes),suspect.to_domain))
        else:
            self.logger.debug('Loading attachment rules from filesystem')
            user_names=self.rulescache.getNAMERules(suspect.to_address)
            user_ctypes=self.rulescache.getCTYPERules(suspect.to_address)
    
            domain_names=self.rulescache.getNAMERules(suspect.to_domain)
            domain_ctypes=self.rulescache.getCTYPERules(suspect.to_domain)

        #always get defaults from file
        default_names=self.rulescache.getNAMERules(FUATT_DEFAULT)
        default_ctypes=self.rulescache.getCTYPERules(FUATT_DEFAULT)

        m=suspect.get_message_rep()
        for i in m.walk():
            if i.is_multipart():
                continue
            contenttype_mime=i.get_content_type()
            att_name = i.get_filename(None)

            if not att_name:
                #workaround for mimetypes, it always takes .ksh for text/plain
                if i.get_content_type()=='text/plain':
                    ext='.txt'
                else:
                    ext = mimetypes.guess_extension(i.get_content_type())

                if ext==None:
                    ext=''
                att_name = 'unnamed%s' % ext

            

            res=self.matchMultipleSets([user_names,domain_names,default_names], att_name,suspect,att_name)
            if res==ATTACHMENT_SILENTDELETE:
                self._debuginfo(suspect,"Attachment name=%s SILENT DELETE : blocked by name"%att_name)
                return DELETE
            if res==ATTACHMENT_BLOCK:
                self._debuginfo(suspect,"Attachment name=%s : blocked by name)"%att_name)
                message=suspect.tags['FiletypePlugin.errormessage']
                return blockactioncode,message
            

            #go through content type rules
            res=self.matchMultipleSets([user_ctypes,domain_ctypes,default_ctypes], contenttype_mime,suspect,att_name)
            if res==ATTACHMENT_SILENTDELETE:
                self._debuginfo(suspect,"Attachment name=%s content-type=%s SILENT DELETE: blocked by mime content type (message source)"%(att_name,contenttype_mime))
                return DELETE
            if res==ATTACHMENT_BLOCK:
                self._debuginfo(suspect,"Attachment name=%s content-type=%s : blocked by mime content type (message source)"%(att_name,contenttype_mime))
                message=suspect.tags['FiletypePlugin.errormessage']
                return blockactioncode,message
            
            if MAGIC_AVAILABLE:
                pl = i.get_payload(decode=True)
                contenttype_magic=self.getBuffertype(pl)
                res=self.matchMultipleSets([user_ctypes,domain_ctypes,default_ctypes], contenttype_magic,suspect,att_name)
                if res==ATTACHMENT_SILENTDELETE:
                    self._debuginfo(suspect,"Attachment name=%s content-type=%s SILENT DELETE: blocked by mime content type (magic)"%(att_name,contenttype_mime))
                    return DELETE
                if res==ATTACHMENT_BLOCK:
                    self._debuginfo(suspect,"Attachment name=%s content-type=%s : blocked by mime content type (magic)"%(att_name,contenttype_mime))
                    message=suspect.tags['FiletypePlugin.errormessage']
                    return blockactioncode,message
        return DUNNO
Example #45
0
    def walk(self, suspect):
        """walks through a message and checks each attachment according to the rulefile specified in the config"""

        blockaction = self.config.get(self.section, 'blockaction')
        blockactioncode = string_to_actioncode(blockaction)

        # try db rules first
        self.rulescache.reloadifnecessary()
        dbconn = ''
        if self.config.has_option(self.section, 'dbconnectstring'):
            dbconn = self.config.get(self.section, 'dbconnectstring')

        if dbconn.strip() != '':
            self.logger.debug('%s Loading attachment rules from database' %
                              suspect.id)
            query = self.config.get(self.section, 'query')
            dbfile = DBFile(dbconn, query)
            user_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_address,
                    'checktype': FUATT_CHECKTYPE_FN
                }))
            user_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_address,
                    'checktype': FUATT_CHECKTYPE_CT
                }))
            user_archive_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_address,
                    'checktype': FUATT_CHECKTYPE_ARCHIVE_FN
                }))
            user_archive_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_address,
                    'checktype': FUATT_CHECKTYPE_ARCHIVE_CT
                }))
            self.logger.debug(
                '%s Found %s filename rules, %s content-type rules, %s archive filename rules, %s archive content rules for address %s'
                % (suspect.id, len(user_names), len(user_ctypes),
                   len(user_archive_names), len(user_archive_ctypes),
                   suspect.to_address))

            domain_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_domain,
                    'checktype': FUATT_CHECKTYPE_FN
                }))
            domain_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_domain,
                    'checktype': FUATT_CHECKTYPE_CT
                }))
            domain_archive_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_domain,
                    'checktype': FUATT_CHECKTYPE_ARCHIVE_FN
                }))
            domain_archive_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({
                    'scope': suspect.to_domain,
                    'checktype': FUATT_CHECKTYPE_ARCHIVE_CT
                }))
            self.logger.debug(
                '%s Found %s filename rules, %s content-type rules, %s archive filename rules, %s archive content rules for domain %s'
                % (suspect.id, len(domain_names), len(domain_ctypes),
                   len(domain_archive_names), len(domain_archive_ctypes),
                   suspect.to_domain))
        else:
            self.logger.debug(
                '%s Loading attachment rules from filesystem dir %s' %
                (suspect.id, self.config.get(self.section, 'rulesdir')))
            user_names = self.rulescache.getNAMERules(suspect.to_address)
            user_ctypes = self.rulescache.getCTYPERules(suspect.to_address)
            user_archive_names = self.rulescache.getARCHIVENAMERules(
                suspect.to_address)
            user_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
                suspect.to_address)

            domain_names = self.rulescache.getNAMERules(suspect.to_domain)
            domain_ctypes = self.rulescache.getCTYPERules(suspect.to_domain)
            domain_archive_names = self.rulescache.getARCHIVENAMERules(
                suspect.to_domain)
            domain_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
                suspect.to_domain)

        # always get defaults from file
        default_names = self.rulescache.getNAMERules(FUATT_DEFAULT)
        default_ctypes = self.rulescache.getCTYPERules(FUATT_DEFAULT)
        default_archive_names = self.rulescache.getARCHIVENAMERules(
            FUATT_DEFAULT)
        default_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
            FUATT_DEFAULT)

        # get mail attachment objects (only directly attached objects)
        for attObj in suspect.att_mgr.get_objectlist():
            contenttype_mime = attObj.contenttype_mime
            att_name = attObj.filename

            if attObj.is_inline or attObj.is_attachment or not attObj.filename_generated:
                # process all attachments marked as "inline", "attachment" or parts
                # with filenames that are not auto-generated
                pass
            else:
                self.logger.debug(
                    "%s Skip message object: %s (attachment: %s, inline: %s, auto-name: %s)"
                    % (suspect.id, att_name, attObj.is_attachment,
                       attObj.is_inline, attObj.filename_generated))
                continue

            att_name = self.asciionly(att_name)

            res = self.matchMultipleSets(
                [user_names, domain_names, default_names], att_name, suspect,
                att_name)
            if res == ATTACHMENT_SILENTDELETE:
                self._debuginfo(
                    suspect,
                    "Attachment name=%s SILENT DELETE : blocked by name" %
                    att_name)
                return DELETE
            if res == ATTACHMENT_BLOCK:
                self._debuginfo(
                    suspect,
                    "Attachment name=%s : blocked by name)" % att_name)
                message = suspect.tags['FiletypePlugin.errormessage']
                return blockactioncode, message

            # go through content type rules
            res = self.matchMultipleSets(
                [user_ctypes, domain_ctypes, default_ctypes], contenttype_mime,
                suspect, att_name)
            if res == ATTACHMENT_SILENTDELETE:
                self._debuginfo(
                    suspect,
                    "Attachment name=%s content-type=%s SILENT DELETE: blocked by mime content type (message source)"
                    % (att_name, contenttype_mime))
                return DELETE
            if res == ATTACHMENT_BLOCK:
                self._debuginfo(
                    suspect,
                    "Attachment name=%s content-type=%s : blocked by mime content type (message source)"
                    % (att_name, contenttype_mime))
                message = suspect.tags['FiletypePlugin.errormessage']
                return blockactioncode, message

            contenttype_magic = attObj.contenttype
            if contenttype_magic is not None:
                res = self.matchMultipleSets(
                    [user_ctypes, domain_ctypes, default_ctypes],
                    contenttype_magic, suspect, att_name)
                if res == ATTACHMENT_SILENTDELETE:
                    self._debuginfo(
                        suspect,
                        "Attachment name=%s content-type=%s SILENT DELETE: blocked by mime content type (magic)"
                        % (att_name, contenttype_magic))
                    return DELETE
                if res == ATTACHMENT_BLOCK:
                    self._debuginfo(
                        suspect,
                        "Attachment name=%s content-type=%s : blocked by mime content type (magic)"
                        % (att_name, contenttype_magic))
                    message = suspect.tags['FiletypePlugin.errormessage']
                    return blockactioncode, message

            # archives
            if self.checkarchivenames or self.checkarchivecontent:

                #if archive_type is not None:
                if attObj.is_archive:

                    # check if extension was used to determine archive type and
                    # if yes, check if extension is enabled. This code
                    # is here to remain backward compatible in the behavior. It
                    # is recommended to define inactive archive-types and -extensions
                    # differently
                    if attObj.atype_fromext() is not None:
                        if not attObj.atype_fromext(
                        ) in self.active_archive_extensions.keys():
                            # skip if extension is not in active list
                            continue

                    self.logger.debug(
                        "%s Extracting %s as %s" %
                        (suspect.id, att_name, attObj.archive_type))
                    archivecontentmaxsize = self.config.getint(
                        self.section, 'archivecontentmaxsize')
                    try:
                        archiveextractlevel = self.config.getint(
                            self.section, 'archiveextractlevel')
                        if archiveextractlevel < 0:  # value must be greater or equals 0
                            archiveextractlevel = None
                    except Exception:
                        archiveextractlevel = None

                    try:

                        if self.checkarchivenames:
                            if self.checkarchivecontent:
                                namelist = attObj.get_fileslist(
                                    0, archiveextractlevel,
                                    archivecontentmaxsize)
                            else:
                                namelist = attObj.fileslist_archive

                            for name in namelist:
                                res = self.matchMultipleSets([
                                    user_archive_names, domain_archive_names,
                                    default_archive_names
                                ], name, suspect, name)
                                if res == ATTACHMENT_SILENTDELETE:
                                    self._debuginfo(
                                        suspect,
                                        "Blocked filename in archive %s SILENT DELETE"
                                        % att_name)
                                    return DELETE
                                if res == ATTACHMENT_BLOCK:
                                    self._debuginfo(
                                        suspect,
                                        "Blocked filename in archive %s" %
                                        att_name)
                                    message = suspect.tags[
                                        'FiletypePlugin.errormessage']
                                    return blockactioncode, message

                        if filetype_handler.available(
                        ) and self.checkarchivecontent:

                            nocheckinfo = NoExtractInfo()
                            for archObj in attObj.get_objectlist(
                                    0,
                                    archiveextractlevel,
                                    archivecontentmaxsize,
                                    noextractinfo=nocheckinfo):
                                safename = self.asciionly(archObj.filename)
                                contenttype_magic = archObj.contenttype

                                # Keeping this check for backward compatibility
                                # This could easily be removed since memory is used anyway
                                if archivecontentmaxsize is not None and archObj.filesize > archivecontentmaxsize:
                                    nocheckinfo.append(
                                        archObj.filename, u"toolarge",
                                        u"already extracted but too large for check: %u > %u"
                                        % (archObj.filesize,
                                           archivecontentmaxsize))
                                    continue

                                res = self.matchMultipleSets([
                                    user_archive_ctypes, domain_archive_ctypes,
                                    default_archive_ctypes
                                ], contenttype_magic, suspect, safename)
                                if res == ATTACHMENT_SILENTDELETE:
                                    self._debuginfo(
                                        suspect,
                                        "Extracted file %s from archive %s content-type=%s "
                                        "SILENT DELETE: blocked by mime content type (magic)"
                                        % (safename, att_name,
                                           contenttype_magic))
                                    return DELETE
                                if res == ATTACHMENT_BLOCK:
                                    self._debuginfo(
                                        suspect,
                                        "Extracted file %s from archive %s content-type=%s : "
                                        "blocked by mime content type (magic)"
                                        % (safename, att_name,
                                           contenttype_magic))
                                    message = suspect.tags[
                                        'FiletypePlugin.errormessage']
                                    return blockactioncode, message

                            for item in nocheckinfo.get_filtered():
                                try:
                                    self._debuginfo(
                                        suspect,
                                        'Archive File not checked: reason: %s -> %s'
                                        % (item[0], item[1]))
                                except Exception as e:
                                    self._debuginfo(
                                        suspect,
                                        'Archive File not checked: %s' %
                                        str(e))

                    except Exception as e:
                        self.logger.error(
                            "%s archive scanning failed in attachment %s: %s" %
                            (suspect.id, att_name, str(e)))
        return DUNNO
Example #46
0
File: sa.py Project: sporkman/fuglu
                self.logger.debug(blvalue)
                # build regex
                # translate glob to regexr
                # http://stackoverflow.com/questions/445910/create-regex-from-glob-expression
                regexp = re.escape(blvalue).replace(
                    r'\?', '.').replace(r'\*', '.*?')
                self.logger.debug(regexp)
                pattern = re.compile(regexp)

                if pattern.search(suspect.from_address):
                    self.logger.debug(
                        'Blacklist match : %s for sa pref %s' % (suspect.from_address, blvalue))
                    confcheck = self.config
                    if runtimeconfig != None:
                        confcheck = runtimeconfig
                    configaction = string_to_actioncode(
                        confcheck.get(self.section, 'highspamaction'), self.config)
                    suspect.tags['spam']['SpamAssassin'] = True
                    prependheader = self.config.get(
                        'main', 'prependaddedheaders')
                    suspect.addheader("%sBlacklisted" % prependheader, blvalue)
                    suspect.debug('Sender is Blacklisted: %s' % blvalue)
                    if configaction == None:
                        return DUNNO
                    return configaction

        return DUNNO

    def _problemcode(self):
        retcode = string_to_actioncode(
            self.config.get(self.section, 'problemaction'), self.config)
        if retcode != None:
Example #47
0
File: sa.py Project: sporkman/fuglu
    def examine(self, suspect):
        # check if someone wants to skip sa checks
        if suspect.get_tag('SAPlugin.skip') == True:
            self.logger.debug(
                'Skipping SA Plugin (requested by previous plugin)')
            suspect.set_tag(
                'SAPlugin.skipreason', 'requested by previous plugin')
            return

        runtimeconfig = DBConfig(self.config, suspect)

        spamsize = suspect.size
        maxsize = self.config.getint(self.section, 'maxsize')
        spamheadername = self.config.get(self.section, 'spamheader')

        if spamsize > maxsize:
            self.logger.info('%s Size Skip, %s > %s' %
                             (suspect.id, spamsize, maxsize))
            suspect.debug('Too big for spamchecks. %s > %s' %
                          (spamsize, maxsize))
            prependheader = self.config.get('main', 'prependaddedheaders')
            suspect.addheader(
                "%sSA-SKIP" % prependheader, 'Too big for spamchecks. %s > %s' % (spamsize, maxsize))
            suspect.set_tag('SAPlugin.skipreason', 'size skip')
            return self.check_sql_blacklist(suspect)

        forwardoriginal = self.config.getboolean(
            self.section, 'forwardoriginal')

        if self.config.getboolean(self.section, 'scanoriginal'):
            spam = suspect.get_original_source()
        else:
            spam = suspect.get_source()

        # prepend temporary headers set by other plugins
        tempheader = suspect.get_tag('SAPlugin.tempheader')
        if tempheader != None:
            if type(tempheader) == list:
                tempheader = "\r\n".join(tempheader)
            tempheader = tempheader.strip()
            if tempheader != '':
                spam = tempheader + '\r\n' + spam

        if forwardoriginal:
            ret = self.safilter_report(spam, suspect.to_address)
            if ret == None:
                suspect.debug('SA report Scan failed - please check error log')
                self.logger.error('%s SA report scan FAILED' % suspect.id)
                suspect.addheader(
                    '%sSA-SKIP' % self.config.get('main', 'prependaddedheaders'), 'SA scan failed')
                suspect.set_tag('SAPlugin.skipreason', 'scan failed')
                return self._problemcode()
            isspam, spamscore, report = ret
            suspect.tags['SAPlugin.report'] = report

        else:
            filtered = self.safilter(spam, suspect.to_address)
            content = None
            if filtered == None:
                suspect.debug('SA Scan failed - please check error log')
                self.logger.error('%s SA scan FAILED' % suspect.id)
                suspect.addheader(
                    '%sSA-SKIP' % self.config.get('main', 'prependaddedheaders'), 'SA scan failed')
                suspect.set_tag('SAPlugin.skipreason', 'scan failed')
                return self._problemcode()
            else:
                content = filtered

            newmsgrep = email.message_from_string(content)
            suspect.set_source(content)
            isspam, spamscore = self._extract_spamstatus(
                newmsgrep, spamheadername, suspect)

        action = DUNNO
        message = None

        if isspam:
            self.logger.debug('Message is spam')
            suspect.debug('Message is spam')

            configaction = string_to_actioncode(
                runtimeconfig.get(self.section, 'lowspamaction'), self.config)
            if configaction != None:
                action = configaction
            values = dict(spamscore=spamscore)
            message = apply_template(
                self.config.get(self.section, 'rejectmessage'), suspect, values)
        else:
            self.logger.debug('Message is not spam')
            suspect.debug('Message is not spam')

        suspect.tags['spam']['SpamAssassin'] = isspam
        suspect.tags['highspam']['SpamAssassin'] = False
        if spamscore != None:
            suspect.tags['SAPlugin.spamscore'] = spamscore
            highspamlevel = runtimeconfig.getint(self.section, 'highspamlevel')
            if spamscore >= highspamlevel:
                suspect.tags['highspam']['SpamAssassin'] = True
                configaction = string_to_actioncode(
                    runtimeconfig.get(self.section, 'highspamaction'), self.config)
                if configaction != None:
                    action = configaction
        return action, message
Example #48
0
 def lint(self):
     viract = self.config.get(self.section, 'virusaction')
     print("Virusaction: %s" % actioncode_to_string(string_to_actioncode(viract, self.config)))
     allok = (self.checkConfig() and self.lint_eicar())
     return allok
Example #49
0
    def examine(self,suspect):
        #check if someone wants to skip sa checks
        if suspect.get_tag('SAPlugin.skip')==True:
            self.logger.debug('Skipping SA Plugin (requested by previous plugin)')
            suspect.set_tag('SAPlugin.skipreason','requested by previous plugin')
            return
        
        runtimeconfig=DBConfig(self.config, suspect)
        
        spamsize=suspect.size        
        maxsize=self.config.getint(self.section, 'maxsize')
        spamheadername=self.config.get(self.section,'spamheader')
        
        if spamsize>maxsize:
            self.logger.info('%s Size Skip, %s > %s'%(suspect.id,spamsize,maxsize))
            suspect.debug('Too big for spamchecks. %s > %s'%(spamsize,maxsize))
            prependheader=self.config.get('main','prependaddedheaders')
            suspect.addheader("%sSA-SKIP"%prependheader, 'Too big for spamchecks. %s > %s'%(spamsize,maxsize))
            suspect.set_tag('SAPlugin.skipreason','size skip')
            return self.check_sql_blacklist(suspect)
            
        
        starttime=time.time()
        
        forwardoriginal=self.config.getboolean(self.section,'forwardoriginal')
        
        if self.config.getboolean(self.section,'scanoriginal'):
            spam=suspect.getOriginalSource()
        else:
            spam=suspect.getSource()
            
        #prepend temporary headers set by other plugins
        tempheader=suspect.get_tag('SAPlugin.tempheader')
        if tempheader!=None:
            if type(tempheader)==list:
                tempheader="\r\n".join(tempheader)
            tempheader=tempheader.strip()
            if tempheader!='':
                spam=tempheader+'\r\n'+spam
            
        if forwardoriginal:
            ret=self.safilter_report(spam, suspect.to_address)
            if ret==None:
                suspect.debug('SA report Scan failed - please check error log')
                self.logger.error('%s SA report scan FAILED'%suspect.id)
                suspect.addheader('%sSA-SKIP'%self.config.get('main','prependaddedheaders'),'SA scan failed')
                suspect.set_tag('SAPlugin.skipreason','scan failed')
                return self._problemcode()
            isspam,spamscore,report=ret
            suspect.tags['SAPlugin.report']=report
            
        else:
            filtered=self.safilter(spam,suspect.to_address)
            content=None
            if filtered==None:
                suspect.debug('SA Scan failed - please check error log')
                self.logger.error('%s SA scan FAILED'%suspect.id)
                suspect.addheader('%sSA-SKIP'%self.config.get('main','prependaddedheaders'),'SA scan failed')
                suspect.set_tag('SAPlugin.skipreason','scan failed')
                return self._problemcode()
            else:
                content=filtered 
            
            newmsgrep=email.message_from_string(content)
            suspect.setSource(content)

            isspam=False
            spamheader=newmsgrep[spamheadername]
        
            spamscore=None
            if spamheader==None:
                self.logger.warning('Did not find Header %s in returned message from SA'%spamheadername)
            else:
                if len(spamheader)>2 and 'yes' in spamheader.lower():
                    isspam=True
                patt=re.compile('Score=([\-\d\.]+)',re.IGNORECASE)
                m=patt.search(spamheader)
                
                if m !=None:
                    spamscore=float(m.group(1))
                    self.logger.debug('Spamscore: %s'%spamscore)
                    suspect.debug('Spamscore: %s'%spamscore)
                else:
                    self.logger.warning('Could not extract spam score from header: %s'%spamheader)
                    suspect.debug('Could not read spam score from header %s'%spamheader)
         
        
        action=DUNNO
        message=None
        
        if isspam:
            self.logger.debug('Message is spam')
            suspect.debug('Message is spam')
            
            configaction=string_to_actioncode(runtimeconfig.get(self.section,'lowspamaction'),self.config)
            if configaction!=None:
                action=configaction
            values=dict(spamscore=spamscore)
            message=apply_template(self.config.get(self.section,'rejectmessage'), suspect, values)
        else:
            self.logger.debug('Message is not spam')
            suspect.debug('Message is not spam')   
        
            
        suspect.tags['spam']['SpamAssassin']=isspam
        suspect.tags['highspam']['SpamAssassin']=False
        if spamscore != None:
            suspect.tags['SAPlugin.spamscore']=spamscore
            highspamlevel=runtimeconfig.getint(self.section,'highspamlevel')
            if spamscore>=highspamlevel:
                suspect.tags['highspam']['SpamAssassin']=True
                configaction=string_to_actioncode(runtimeconfig.get(self.section,'highspamaction'),self.config)
                if configaction!=None:
                    action=configaction
        
        endtime=time.time()
        difftime=endtime-starttime
        suspect.tags['SAPlugin.time']="%.4f"%difftime
        return action,message
Example #50
0
    def walk(self, suspect):
        """walks through a message and checks each attachment according to the rulefile specified in the config"""

        blockaction = self.config.get(self.section, 'blockaction')
        blockactioncode = string_to_actioncode(blockaction)

        # try db rules first
        self.rulescache.reloadifnecessary()
        dbconn = ''
        if self.config.has_option(self.section, 'dbconnectstring'):
            dbconn = self.config.get(self.section, 'dbconnectstring')

        if dbconn.strip() != '':
            self.logger.debug('Loading attachment rules from database')
            query = self.config.get(self.section, 'query')
            dbfile = DBFile(dbconn, query)
            user_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_address, 'checktype': FUATT_CHECKTYPE_FN}))
            user_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_address, 'checktype': FUATT_CHECKTYPE_CT}))
            user_archive_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_address, 'checktype': FUATT_CHECKTYPE_ARCHIVE_FN}))
            user_archive_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_address, 'checktype': FUATT_CHECKTYPE_ARCHIVE_CT}))
            self.logger.debug('Found %s filename rules, %s content-type rules, %s archive filename rules, %s archive content rules for address %s' %
                              (len(user_names), len(user_ctypes), len(user_archive_names), len(user_archive_ctypes), suspect.to_address))

            domain_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_domain, 'checktype': FUATT_CHECKTYPE_FN}))
            domain_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_domain, 'checktype': FUATT_CHECKTYPE_CT}))
            domain_archive_names = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_domain, 'checktype': FUATT_CHECKTYPE_ARCHIVE_FN}))
            domain_archive_ctypes = self.rulescache.get_rules_from_config_lines(
                dbfile.getContent({'scope': suspect.to_domain, 'checktype': FUATT_CHECKTYPE_ARCHIVE_CT}))
            self.logger.debug('Found %s filename rules, %s content-type rules, %s archive filename rules, %s archive content rules for domain %s' %
                              (len(domain_names), len(domain_ctypes), len(domain_archive_names), len(domain_archive_ctypes), suspect.to_domain))
        else:
            self.logger.debug('Loading attachment rules from filesystem')
            user_names = self.rulescache.getNAMERules(suspect.to_address)
            user_ctypes = self.rulescache.getCTYPERules(suspect.to_address)
            user_archive_names = self.rulescache.getARCHIVENAMERules(
                suspect.to_address)
            user_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
                suspect.to_address)

            domain_names = self.rulescache.getNAMERules(suspect.to_domain)
            domain_ctypes = self.rulescache.getCTYPERules(suspect.to_domain)
            domain_archive_names = self.rulescache.getARCHIVENAMERules(
                suspect.to_domain)
            domain_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
                suspect.to_domain)

        # always get defaults from file
        default_names = self.rulescache.getNAMERules(FUATT_DEFAULT)
        default_ctypes = self.rulescache.getCTYPERules(FUATT_DEFAULT)
        default_archive_names = self.rulescache.getARCHIVENAMERules(
            FUATT_DEFAULT)
        default_archive_ctypes = self.rulescache.getARCHIVECTYPERules(
            FUATT_DEFAULT)

        m = suspect.get_message_rep()
        for i in m.walk():
            if i.is_multipart():
                continue
            contenttype_mime = i.get_content_type()
            att_name = i.get_filename(None)

            if att_name:
                # some filenames are encoded, try to decode
                try:
                    att_name = ''.join([x[0] for x in decode_header(att_name)])
                except:
                    pass
            else:
                # workaround for mimetypes, it always takes .ksh for text/plain
                if i.get_content_type() == 'text/plain':
                    ext = '.txt'
                else:
                    ext = mimetypes.guess_extension(i.get_content_type())

                if ext == None:
                    ext = ''
                att_name = 'unnamed%s' % ext

            res = self.matchMultipleSets(
                [user_names, domain_names, default_names], att_name, suspect, att_name)
            if res == ATTACHMENT_SILENTDELETE:
                self._debuginfo(
                    suspect, "Attachment name=%s SILENT DELETE : blocked by name" % att_name)
                return DELETE
            if res == ATTACHMENT_BLOCK:
                self._debuginfo(
                    suspect, "Attachment name=%s : blocked by name)" % att_name)
                message = suspect.tags['FiletypePlugin.errormessage']
                return blockactioncode, message

            # go through content type rules
            res = self.matchMultipleSets(
                [user_ctypes, domain_ctypes, default_ctypes], contenttype_mime, suspect, att_name)
            if res == ATTACHMENT_SILENTDELETE:
                self._debuginfo(
                    suspect, "Attachment name=%s content-type=%s SILENT DELETE: blocked by mime content type (message source)" % (att_name, contenttype_mime))
                return DELETE
            if res == ATTACHMENT_BLOCK:
                self._debuginfo(
                    suspect, "Attachment name=%s content-type=%s : blocked by mime content type (message source)" % (att_name, contenttype_mime))
                message = suspect.tags['FiletypePlugin.errormessage']
                return blockactioncode, message

            if MAGIC_AVAILABLE:
                pl = i.get_payload(decode=True)
                contenttype_magic = self.getBuffertype(pl)
                res = self.matchMultipleSets(
                    [user_ctypes, domain_ctypes, default_ctypes], contenttype_magic, suspect, att_name)
                if res == ATTACHMENT_SILENTDELETE:
                    self._debuginfo(
                        suspect, "Attachment name=%s content-type=%s SILENT DELETE: blocked by mime content type (magic)" % (att_name, contenttype_mime))
                    return DELETE
                if res == ATTACHMENT_BLOCK:
                    self._debuginfo(
                        suspect, "Attachment name=%s content-type=%s : blocked by mime content type (magic)" % (att_name, contenttype_mime))
                    message = suspect.tags['FiletypePlugin.errormessage']
                    return blockactioncode, message

            # archives
            if self.config.getboolean(self.section, 'checkarchivenames') or self.config.getboolean(self.section, 'checkarchivecontent'):
                archive_type = None
                for arext in self.supported_archive_extensions:
                    if att_name.lower().endswith('.%s' % arext):
                        archive_type = arext
                        break

                if archive_type != None:
                    try:
                        pl = StringIO(i.get_payload(decode=True))
                        archive_handle = self._archive_handle(archive_type, pl)
                        namelist = self._archive_namelist(
                            archive_type, archive_handle)
                        if self.config.getboolean(self.section, 'checkarchivenames'):
                            for name in namelist:
                                # rarfile returns unicode objects which mess up
                                # generated bounces
                                if type(name) == unicode:
                                    name = name.encode("utf-8", "ignore")
                                res = self.matchMultipleSets(
                                    [user_archive_names, domain_archive_names, default_archive_names], name, suspect, name)
                                if res == ATTACHMENT_SILENTDELETE:
                                    self._debuginfo(
                                        suspect, "Blocked filename in archive %s SILENT DELETE" % att_name)
                                    return DELETE
                                if res == ATTACHMENT_BLOCK:
                                    self._debuginfo(
                                        suspect, "Blocked filename in archive %s" % att_name)
                                    message = suspect.tags[
                                        'FiletypePlugin.errormessage']
                                    return blockactioncode, message

                        if MAGIC_AVAILABLE and self.config.getboolean(self.section, 'checkarchivecontent'):
                            for name in namelist:
                                safename = self.asciionly(name)
                                extracted = self._archive_extract(
                                    archive_type, archive_handle, name)
                                if extracted == None:
                                    self._debuginfo(
                                        suspect, '%s not extracted - too large' % (safename))
                                contenttype_magic = self.getBuffertype(
                                    extracted)
                                res = self.matchMultipleSets(
                                    [user_archive_ctypes, domain_archive_ctypes, default_archive_ctypes], contenttype_magic, suspect, name)
                                if res == ATTACHMENT_SILENTDELETE:
                                    self._debuginfo(
                                        suspect, "Extracted file %s from archive %s content-type=%s SILENT DELETE: blocked by mime content type (magic)" % (safename, att_name, contenttype_magic))
                                    return DELETE
                                if res == ATTACHMENT_BLOCK:
                                    self._debuginfo(
                                        suspect, "Extracted file %s from archive %s content-type=%s : blocked by mime content type (magic)" % (safename, att_name, contenttype_magic))
                                    message = suspect.tags[
                                        'FiletypePlugin.errormessage']
                                    return blockactioncode, message

                    except Exception as e:
                        self.logger.warning(
                            "archive scanning failed in attachment %s: %s" % (att_name, str(e)))
        return DUNNO
Example #51
0
File: sa.py Project: danBLA/fuglu
 def examine(self, suspect):
     # check if someone wants to skip sa checks
     if suspect.get_tag('SAPlugin.skip') is True:
         self.logger.debug('%s Skipping SA Plugin (requested by previous plugin)' % suspect.id)
         suspect.set_tag('SAPlugin.skipreason', 'requested by previous plugin')
         return DUNNO
     
     runtimeconfig = DBConfig(self.config, suspect)
     
     spamsize = suspect.size
     maxsize = self.config.getint(self.section, 'maxsize')
     strip_oversize = self.config.getboolean(self.section, 'strip_oversize')
     
     if spamsize > maxsize and not strip_oversize:
         self.logger.info('%s Size Skip, %s > %s' % (suspect.id, spamsize, maxsize))
         suspect.debug('Too big for spamchecks. %s > %s' % (spamsize, maxsize))
         prependheader = self.config.get('main', 'prependaddedheaders')
         suspect.addheader("%sSA-SKIP" % prependheader, 'Too big for spamchecks. %s > %s' % (spamsize, maxsize))
         suspect.set_tag('SAPlugin.skipreason', 'size skip')
         return self.check_sql_blacklist(suspect)
     
     if self.config.getboolean(self.section, 'scanoriginal'):
         content = suspect.get_original_source()
     else:
         content = suspect.get_source()
     
     stripped = False
     if spamsize > maxsize:
         stripped = True
         # keep copy of original content before stripping
         content_orig = content
         # send maxsize-1 to be consistent with previous implementation
         content = suspect.source_stripped_attachments(content=content, maxsize=maxsize-1)
         self.logger.info('%s stripped attachments, body size reduced from %s to %s bytes' % (suspect.id, len(content_orig), len(content)))
     # stick to bytes
     content = force_bString(content)
     
     # prepend temporary headers set by other plugins
     tempheader = suspect.get_tag('SAPlugin.tempheader')
     if tempheader is not None:
         if isinstance(tempheader, list):
             tempheader = "\r\n".join(tempheader)
         tempheader = tempheader.strip()
         if tempheader != '':
             content = force_bString(tempheader + '\r\n') + content
     
     # add envelope sender information
     msgrep = suspect.get_message_rep()
     if not 'Return-Path' in msgrep.keys():
         content = force_bString('Return-Path: %s' % suspect.from_address + '\r\n') + content
     
     forwardoriginal = self.config.getboolean(self.section, 'forwardoriginal')
     if forwardoriginal:
         ret = self.safilter_report(content, suspect.to_address)
         if ret is None:
             suspect.debug('SA report Scan failed - please check error log')
             self.logger.error('%s SA report scan FAILED' % suspect.id)
             suspect.addheader('%sSA-SKIP' % self.config.get('main', 'prependaddedheaders'), 'SA scan failed')
             suspect.set_tag('SAPlugin.skipreason', 'scan failed')
             return self._problemcode()
         isspam, spamscore, report = ret
         suspect.tags['SAPlugin.report'] = report
     
     else:
         filtered = self.safilter(content, suspect.to_address)
         if filtered is None:
             suspect.debug('SA Scan failed - please check error log')
             self.logger.error('%s SA scan FAILED' % suspect.id)
             suspect.addheader('%sSA-SKIP' % self.config.get('main', 'prependaddedheaders'), 'SA scan failed')
             suspect.set_tag('SAPlugin.skipreason', 'scan failed')
             return self._problemcode()
         else:
             if stripped:
                 # create msgrep of filtered msg
                 if isinstance(content,str):
                     msgrep_filtered = email.message_from_string(filtered, _class=PatchedMessage)
                 else:
                     msgrep_filtered = email.message_from_bytes(filtered, _class=PatchedMessage)
                 header_new = []
                 for h,v in msgrep_filtered.items():
                     header_new.append(force_uString(h).strip() + ': ' + force_uString(v).strip())
                 # add headers to msg
                 sa_prepend = self.config.get(self.section, 'spamheader_prepend')
                 for i in header_new:
                     if sa_prepend == '' or sa_prepend is None:
                         break
                     if re.match('^' + sa_prepend + '[^:]+: ', i, re.I):
                         # in case of stripped msg add header to original content
                         content_orig = force_bString(i) + b'\r\n' + force_bString(content_orig)
                     else:
                         continue
                 content = content_orig
             else:
                 content = filtered
                 
         if isinstance(content,str):
             newmsgrep = email.message_from_string(content, _class=PatchedMessage)
         else:
             newmsgrep = email.message_from_bytes(content, _class=PatchedMessage)
         
         # if original content is forwarded there's no need to reset the attachmant
         # manager. Only header have been changed.
         suspect.set_source(content,att_mgr_reset=(not forwardoriginal))
         spamheadername = self.config.get(self.section, 'spamheader')
         isspam, spamscore, report = self._extract_spamstatus(newmsgrep, spamheadername, suspect)
         suspect.tags['SAPlugin.report'] = report
         self.logger.debug('suspect %s %s %s %s' % (suspect.id, isspam, spamscore, suspect.get_tag('SAPlugin.report')))
     
     action = DUNNO
     message = None
     
     if isspam:
         self.logger.debug('%s Message is spam' % suspect.id)
         suspect.debug('Message is spam')
         
         configaction = string_to_actioncode(
             runtimeconfig.get(self.section, 'lowspamaction'), self.config)
         if configaction is not None:
             action = configaction
         values = dict(spamscore=spamscore)
         message = apply_template(
             self.config.get(self.section, 'rejectmessage'), suspect, values)
     else:
         self.logger.debug('%s Message is not spam' % suspect.id)
         suspect.debug('Message is not spam')
     
     suspect.tags['spam']['SpamAssassin'] = isspam
     suspect.tags['highspam']['SpamAssassin'] = False
     if spamscore is not None:
         suspect.tags['SAPlugin.spamscore'] = spamscore
         highspamlevel = runtimeconfig.getfloat(self.section, 'highspamlevel')
         if spamscore >= highspamlevel:
             suspect.tags['highspam']['SpamAssassin'] = True
             configaction = string_to_actioncode(runtimeconfig.get(self.section, 'highspamaction'), self.config)
             if configaction is not None:
                 action = configaction
     return action, message
Example #52
0
                # build regex
                # translate glob to regexr
                # http://stackoverflow.com/questions/445910/create-regex-from-glob-expression
                regexp = re.escape(blvalue).replace(r'\?',
                                                    '.').replace(r'\*', '.*?')
                self.logger.debug(regexp)
                pattern = re.compile(regexp)

                if pattern.search(suspect.from_address):
                    self.logger.debug('Blacklist match : %s for sa pref %s' %
                                      (suspect.from_address, blvalue))
                    confcheck = self.config
                    if runtimeconfig != None:
                        confcheck = runtimeconfig
                    configaction = string_to_actioncode(
                        confcheck.get(self.section, 'highspamaction'),
                        self.config)
                    suspect.tags['spam']['SpamAssassin'] = True
                    prependheader = self.config.get('main',
                                                    'prependaddedheaders')
                    suspect.addheader("%sBlacklisted" % prependheader, blvalue)
                    suspect.debug('Sender is Blacklisted: %s' % blvalue)
                    if configaction == None:
                        return DUNNO
                    return configaction

        return DUNNO

    def _problemcode(self):
        retcode = string_to_actioncode(
            self.config.get(self.section, 'problemaction'), self.config)
Example #53
0
    def examine(self, suspect):
        # check if someone wants to skip sa checks
        if suspect.get_tag('SAPlugin.skip') is True:
            self.logger.debug(
                '%s Skipping SA Plugin (requested by previous plugin)' %
                suspect.id)
            suspect.set_tag('SAPlugin.skipreason',
                            'requested by previous plugin')
            return DUNNO

        runtimeconfig = DBConfig(self.config, suspect)

        spamsize = suspect.size
        maxsize = self.config.getint(self.section, 'maxsize')
        strip_oversize = self.config.getboolean(self.section, 'strip_oversize')

        if spamsize > maxsize and not strip_oversize:
            self.logger.info('%s Size Skip, %s > %s' %
                             (suspect.id, spamsize, maxsize))
            suspect.debug('Too big for spamchecks. %s > %s' %
                          (spamsize, maxsize))
            prependheader = self.config.get('main', 'prependaddedheaders')
            suspect.addheader(
                "%sSA-SKIP" % prependheader,
                'Too big for spamchecks. %s > %s' % (spamsize, maxsize))
            suspect.set_tag('SAPlugin.skipreason', 'size skip')
            return self.check_sql_blacklist(suspect)

        if self.config.getboolean(self.section, 'scanoriginal'):
            content = suspect.get_original_source()
        else:
            content = suspect.get_source()

        stripped = False
        if spamsize > maxsize:
            stripped = True
            # keep copy of original content before stripping
            content_orig = content
            content = self._strip_attachments(content, maxsize)
            self.logger.info(
                '%s stripped attachments, body size reduced from %s to %s bytes'
                % (suspect.id, len(content_orig), len(content)))
        # stick to bytes
        content = force_bString(content)

        # prepend temporary headers set by other plugins
        tempheader = suspect.get_tag('SAPlugin.tempheader')
        if tempheader is not None:
            if isinstance(tempheader, list):
                tempheader = "\r\n".join(tempheader)
            tempheader = tempheader.strip()
            if tempheader != '':
                content = force_bString(tempheader + '\r\n') + content

        forwardoriginal = self.config.getboolean(self.section,
                                                 'forwardoriginal')
        if forwardoriginal:
            ret = self.safilter_report(content, suspect.to_address)
            if ret is None:
                suspect.debug('SA report Scan failed - please check error log')
                self.logger.error('%s SA report scan FAILED' % suspect.id)
                suspect.addheader(
                    '%sSA-SKIP' %
                    self.config.get('main', 'prependaddedheaders'),
                    'SA scan failed')
                suspect.set_tag('SAPlugin.skipreason', 'scan failed')
                return self._problemcode()
            isspam, spamscore, report = ret
            suspect.tags['SAPlugin.report'] = report

        else:
            filtered = self.safilter(content, suspect.to_address)
            if filtered is None:
                suspect.debug('SA Scan failed - please check error log')
                self.logger.error('%s SA scan FAILED' % suspect.id)
                suspect.addheader(
                    '%sSA-SKIP' %
                    self.config.get('main', 'prependaddedheaders'),
                    'SA scan failed')
                suspect.set_tag('SAPlugin.skipreason', 'scan failed')
                return self._problemcode()
            else:
                if stripped:
                    # create msgrep of filtered msg
                    msgrep_filtered = email.message_from_string(filtered)
                    header_new = []
                    header_old = []
                    # create a msgrep from original msg
                    msgrep_orig = email.message_from_string(content_orig)
                    # read all headers from after-scan and before-scan
                    for h, v in msgrep_filtered.items():
                        header_new.append(h.strip() + ': ' + v.strip())
                    for h, v in msgrep_orig.items():
                        header_old.append(h.strip() + ': ' + v.strip())
                    # create a list of headers added by spamd
                    # header diff between before-scan and after-scan msg
                    header_new = reversed(self.diff(header_new, header_old))
                    # add headers to msg
                    for i in header_new:
                        if re.match('^Received: ', i, re.I):
                            continue
                        # in case of stripped msg add header to original content
                        content_orig = i + '\r\n' + content_orig
                    content = content_orig
                else:
                    content = filtered
            if sys.version_info > (3, ):
                # Python 3 and larger
                # the basic "str" type is unicode
                if isinstance(content, str):
                    newmsgrep = email.message_from_string(content)
                else:
                    newmsgrep = email.message_from_bytes(content)
            else:
                # Python 2.x
                newmsgrep = email.message_from_string(content)
            suspect.set_source(content)
            spamheadername = self.config.get(self.section, 'spamheader')
            isspam, spamscore, report = self._extract_spamstatus(
                newmsgrep, spamheadername, suspect)
            suspect.tags['SAPlugin.report'] = report
            self.logger.debug('suspect %s %s %s %s' %
                              (suspect.id, isspam, spamscore,
                               suspect.get_tag('SAPlugin.report')))

        action = DUNNO
        message = None

        if isspam:
            self.logger.debug('%s Message is spam' % suspect.id)
            suspect.debug('Message is spam')

            configaction = string_to_actioncode(
                runtimeconfig.get(self.section, 'lowspamaction'), self.config)
            if configaction is not None:
                action = configaction
            values = dict(spamscore=spamscore)
            message = apply_template(
                self.config.get(self.section, 'rejectmessage'), suspect,
                values)
        else:
            self.logger.debug('%s Message is not spam' % suspect.id)
            suspect.debug('Message is not spam')

        suspect.tags['spam']['SpamAssassin'] = isspam
        suspect.tags['highspam']['SpamAssassin'] = False
        if spamscore is not None:
            suspect.tags['SAPlugin.spamscore'] = spamscore
            highspamlevel = runtimeconfig.getfloat(self.section,
                                                   'highspamlevel')
            if spamscore >= highspamlevel:
                suspect.tags['highspam']['SpamAssassin'] = True
                configaction = string_to_actioncode(
                    runtimeconfig.get(self.section, 'highspamaction'),
                    self.config)
                if configaction is not None:
                    action = configaction
        return action, message