コード例 #1
0
ファイル: esmtpconnector.py プロジェクト: danBLA/fuglu
 def endsession(self, code, message):
     """End session with incoming postfix"""
     self.socket.send(force_bString("%s %s\r\n" % (code, message)))
     rawdata = b''
     data = ''
     while True:
         lump = self.socket.recv(1024)
         if len(lump):
             rawdata += lump
             if (len(rawdata) >= 2) and rawdata[-2:] == b'\r\n':
                 cmd = data[0:4]
                 cmd = cmd.upper()
                 if cmd == b"QUIT":
                     self.socket.send(
                         force_bString("%s %s\r\n" % (220, "BYE")))
                     self.closeconn()
                     return
                 self.socket.send(
                     force_bString("%s %s\r\n" %
                                   (421, "Cannot accept further commands")))
                 self.closeconn()
                 return
         else:
             self.closeconn()
             return
コード例 #2
0
    def scan_stream(self, content, suspectid='(NA)'):
        """
        Scan a buffer

        content (string) : buffer to scan

        return either :
          - (dict) : {filename1: "virusname"}
          - None if no virus found
        """

        s = self.__init_socket__()
        content = force_bString(content)
        buflen = len(content)
        s.sendall(force_bString('SCAN %s STREAM fu_stream SIZE %s' % (self.config.get(self.section, 'scanoptions'), buflen)))
        s.sendall(b'\n')
        self.logger.debug('%s Sending buffer (length=%s) to fpscand...' % (suspectid, buflen))
        s.sendall(content)
        self.logger.debug('%s Sent %s bytes to fpscand, waiting for scan result' % (suspectid, buflen))

        result = force_uString(s.recv(20000))
        if len(result) < 1:
            self.logger.error('Got no reply from fpscand')
        s.close()

        return self._parse_result(result)
コード例 #3
0
ファイル: domainauth.py プロジェクト: danBLA/fuglu
    def examine(self, suspect):
        if not DKIMPY_AVAILABLE:
            suspect.debug("dkimpy not available, can not check")
            self.logger.error("DKIM signing skipped - missing dkimpy library")
            return DUNNO

        message = suspect.get_source()
        domain = extract_from_domain(suspect)
        addvalues = dict(header_from_domain=domain)
        selector = apply_template(self.config.get(self.section, 'selector'),
                                  suspect, addvalues)

        if domain is None:
            self.logger.error(
                "%s: Failed to extract From-header domain for DKIM signing" %
                suspect.id)
            return DUNNO

        privkeyfile = apply_template(
            self.config.get(self.section, 'privatekeyfile'), suspect,
            addvalues)
        if not os.path.isfile(privkeyfile):
            self.logger.debug(
                "%s: DKIM signing failed for domain %s, private key not found: %s"
                % (suspect.id, domain, privkeyfile))
            return DUNNO

        with open(privkeyfile, 'br') as f:
            privkeycontent = f.read()

        canH = Simple
        canB = Simple

        if self.config.get(self.section,
                           'canonicalizeheaders').lower() == 'relaxed':
            canH = Relaxed
        if self.config.get(self.section,
                           'canonicalizebody').lower() == 'relaxed':
            canB = Relaxed
        canon = (canH, canB)
        headerconfig = self.config.get(self.section, 'signheaders')
        if headerconfig is None or headerconfig.strip() == '':
            inc_headers = None
        else:
            inc_headers = headerconfig.strip().split(',')

        blength = self.config.getboolean(self.section, 'signbodylength')

        dkimhdr = sign(message,
                       force_bString(selector),
                       force_bString(domain),
                       privkeycontent,
                       canonicalize=canon,
                       include_headers=inc_headers,
                       length=blength,
                       logger=suspect.get_tag('debugfile'))
        if dkimhdr.startswith(b'DKIM-Signature: '):
            dkimhdr = dkimhdr[16:]

        suspect.addheader('DKIM-Signature', dkimhdr, immediate=True)
コード例 #4
0
    def test_bounce(self):
        """Test bounce message, especially the encoding"""
        suspect = Suspect('*****@*****.**',
                          '*****@*****.**', '/dev/null')

        # include non-ascii charset unicode characters to make sure the encoding/decoding
        # works correctly
        displayname = u"((testing placeholder for displayname -> äää))"
        asciirep = u"((testing placeholder for asciirep -> üüü))"
        description = u"((testing placeholder for description -> ööö))"

        blockinfo = ("%s %s: %s" %
                     (displayname, asciirep, description)).strip()
        blockedfiletemplate = os.path.join(
            *[CONFDIR, "templates", "blockedfile.tmpl.dist"])

        bounce = Bounce(self.config)
        bounce.send_template_file(suspect.from_address, blockedfiletemplate,
                                  suspect, dict(blockinfo=blockinfo))

        # might be needed to wait for a bit to make sure answer is available
        counter = 0
        while self.smtp.suspect is None and counter < 20:
            counter = counter + 1
            time.sleep(0.05)  # sleep is needed to

        gotback = self.smtp.suspect
        self.assertFalse(gotback == None,
                         "Did not get message from dummy smtp server")

        # get message received by dummy smtp server
        msg = gotback.get_message_rep()
        receivedMsg = msg.get_payload(decode='utf-8')

        # Build the message according to what Bounce is doing so it can be compared
        # to what was received from DummySMTPServer
        with open(blockedfiletemplate) as fp:
            templatecontent = fp.read()

        blockinfo = ("%s %s: %s" %
                     (displayname, asciirep, description)).strip()
        message = apply_template(templatecontent, suspect,
                                 dict(blockinfo=blockinfo))
        messageB = force_bString(message)

        # modify received message to add header parts from template
        messageToCompare = force_bString("To: " + msg['To'] + "\nSubject: " +
                                         msg['Subject'] +
                                         "\n\n") + force_bString(receivedMsg)

        # make sure comparison will not fail because of newlines
        # For example, Python 2.6 has only one "\n" at the end of the received message, whereas Python 2.7 and 3 have to
        messageToCompare = messageToCompare.replace(b"\r", b"\n").replace(
            b"\n\n", b"\n")
        messageB = messageB.replace(b"\r", b"\n").replace(b"\n\n", b"\n")

        self.assertEqual(messageB, messageToCompare)
コード例 #5
0
    def test_encode2bytes(self):
        """Test if strings are correctly encoded"""
        self.assertEqual(bytes, type(force_bString("bla")),
                         "After byte conversion, type has to be bytes")
        self.assertEqual(bytes, type(force_bString(u"bla")),
                         "After byte conversion, type has to be bytes")
        self.assertEqual(bytes, type(force_bString(b"bla")),
                         "After byte conversion, type has to be bytes")

        mixedlist = ["bla", u"bla", b"bla"]
        for item in force_bString(mixedlist):
            self.assertEqual(bytes, type(item),
                             "After byte conversion, type has to be bytes")
            self.assertEqual(b"bla", item,
                             "String has to match the test string b\"bla\"")
コード例 #6
0
ファイル: endtoend_test.py プロジェクト: danBLA/fuglu
    def test_SMTPUTF8_E2E(self):
        """test if a UTF-8 message runs through"""

        # give fuglu time to start listener
        time.sleep(1)

        root = logging.getLogger()
        root.setLevel(logging.DEBUG)
        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.DEBUG)
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        ch.setFormatter(formatter)
        root.addHandler(ch)

        # send test message
        smtpclient = smtplib.SMTP('127.0.0.1', EndtoEndBaseTestCase.FUGLU_PORT)
        # smtpServer.set_debuglevel(1)
        (code, msg) = smtpclient.ehlo('test.e2e')
        msg = force_uString(msg.split())

        self.assertEqual(250, code)
        print("%s"%msg)
        self.assertIn("SMTPUTF8", msg)

        testunicodemessage = u"""Hello Wörld!\r
Don't där yü tschänsch äny of mai baits or iwen remüv ön!"""

        # TODO: this test fails if we don't put in the \r in there... (eg,
        # fuglu adds it) - is this a bug or wrong test?

        msg = MIMEText(testunicodemessage, _charset='utf-8')
        msg["Subject"] = "End to End Test"
        msgstring = msg.as_string()
        inbytes = len(msg.get_payload(decode=True))
        # envelope sender/recipients
        env_sender = u'sä[email protected]'
        env_recipients = [u'rö[email protected]', u'récipiè[email protected]']
        smtpclient.sendmail(force_uString(env_sender),
                            force_uString(env_recipients),
                            force_bString(msgstring), mail_options=["SMTPUTF8"])
        smtpclient.quit()

        # get answer (wait to give time to create suspect)
        time.sleep(0.1)
        gotback = self.smtp.suspect
        self.assertFalse(gotback == None, "Did not get message from dummy smtp server")

        # check a few things on the received message
        msgrep = gotback.get_message_rep()
        self.assertTrue('X-Fuglutest-Spamstatus' in msgrep,
                        "Fuglu SPAM Header not found in message")
        payload = msgrep.get_payload(decode=True)
        outbytes = len(payload)
        self.assertEqual(inbytes, outbytes,"Message size change: bytes in: %u, bytes out %u" % (inbytes, outbytes))
        self.assertEqual(testunicodemessage, force_uString(payload),
                         "Message body has been altered. In: %u bytes, Out: %u bytes, teststring=->%s<- result=->%s<-" %
                         (inbytes, outbytes, testunicodemessage, force_uString(payload)))
        # check sender/recipients
        self.assertEqual(env_sender, gotback.from_address)
        self.assertEqual(env_recipients, gotback.recipients)
コード例 #7
0
ファイル: debug.py プロジェクト: danBLA/fuglu
    def handlesession(self):
        line = force_uString(self.socket.recv(4096)).strip()
        if line == '':
            self.socket.close()
            return

        self.logger.debug('Control Socket command: %s' % line)
        answer = None
        try:
            if line.startswith("objgraph"):
                # special handling for objgraph
                # -> argument is a dict in json format
                # -> check attributes for commands, don't use list
                parts = line.split(maxsplit=1)
                if len(parts) == 2:
                    argsdict = ControlSession.json_string_to_obj(
                        parts[1], ForcedType=dict)
                else:
                    argsdict = {}
                self.logger.debug('objgraph_growth: args dict: %s' % argsdict)
                answer = self.handle_command(parts[0],
                                             argsdict,
                                             checkattr=True)
            else:
                # default handling
                line = line.lower()
                parts = line.split()
                answer = self.handle_command(parts[0], parts[1:])
        except Exception as e:
            if not answer:
                answer = force_uString(e)
            else:
                answer += force_uString(e)
        self.socket.sendall(force_bString(answer))
        self.socket.close()
コード例 #8
0
    def doData(self, data):
        """Store data in temporary file

        Args:
            data (str or bytes): data as byte-string

        """
        data = self.unquoteData(data)
        # store the last few bytes in memory to keep track when the msg is
        # finished
        self.dataAccum = self.dataAccum + data

        if len(self.dataAccum) > 4:
            self.dataAccum = self.dataAccum[-5:]

        if len(self.dataAccum) > 4 and self.dataAccum[-5:] == force_bString(
                '\r\n.\r\n'):
            # check if there is more data to write to the file
            if len(data) > 4:
                self.tempfile.write(data[0:-5])

            self._close_tempfile()

            self.state = SMTPSession.ST_HELO
            return "250 OK - Data and terminator. found"
        else:
            self.tempfile.write(data)
            return None
コード例 #9
0
ファイル: a_statsd.py プロジェクト: danBLA/fuglu
    def process(self, suspect, decision):
        recipient = force_uString(
            suspect.to_domain)  # work with unicode string
        if self.config.get(self.section, 'level') == 'email':
            recipient = suspect.to_address
        recipient = recipient.replace('.', '-')
        recipient = recipient.replace('@', '--')

        host = self.config.get(self.section, 'host')
        port = self.config.getint(self.section, 'port')

        buffer = ""
        if self.sock is None:
            addr_f = socket.getaddrinfo(host, 0)[0][0]
            self.sock = socket.socket(addr_f, socket.SOCK_DGRAM)

        if suspect.is_virus():
            buffer = "%s%s.fuglu.recipient.%s.virus:1|c\n" % (
                buffer, self.nodename, recipient)
        elif suspect.is_highspam():
            buffer = "%s%s.fuglu.recipient.%s.highspam:1|c\n" % (
                buffer, self.nodename, recipient)
        elif suspect.is_spam():
            buffer = "%s%s.fuglu.recipient.%s.spam:1|c\n" % (
                buffer, self.nodename, recipient)
        else:
            buffer = "%s%s.fuglu.recipient.%s.clean:1|c\n" % (
                buffer, self.nodename, recipient)

        self.sock.sendto(force_bString(buffer), (host, port))
コード例 #10
0
    def changeheader(self, key, value):
        """
        Change header in message sending corresponding command to MTA
        using protocol stored in self.sess

        Args:
            key (string(encoded)): header key
            value (string(encoded)): header value
        """
        if not self.sess.has_option(lm.SMFIF_CHGHDRS):
            self.logger.error(
                'Change header called without the proper opts set, '
                'availability -> fuglu: %s, mta: %s' %
                (self.sess.has_option(lm.SMFIF_CHGHDRS, client="fuglu"),
                 self.sess.has_option(lm.SMFIF_CHGHDRS, client="mta")))
            return
        self.sess.chgHeader(force_bString(key), force_bString(value))
コード例 #11
0
 def test_int2bytes(self):
     """Test if an integer can be converted to string->bytes"""
     myint = 550
     converted = force_bString(myint)
     self.assertEqual(
         b"550", converted,
         "Integer should be converted to a string and then to bytes")
     pass
コード例 #12
0
ファイル: esmtpconnector.py プロジェクト: danBLA/fuglu
    def getincomingmail(self):
        """return true if mail got in, false on error Session will be kept open"""
        self.socket.send(force_bString("220 fuglu scanner ready \r\n"))

        while True:
            rawdata = b''
            completeLine = 0
            while not completeLine:
                lump = self.socket.recv(1024)
                if len(lump):
                    rawdata += lump
                    if (len(rawdata) >= 2) and rawdata[-2:] == b'\r\n':
                        completeLine = 1

                        if self.state != ESMTPPassthroughSession.ST_DATA:
                            # decode message (except data) from binary to unicode
                            data = force_uString(rawdata)
                            rsp, keep = self.doCommand(data)
                        else:
                            try:
                                rsp = self.doData(rawdata)
                            except IOError:
                                self.endsession(
                                    421, "Could not write to temp file")
                                self._close_tempfile()
                                return False

                            if rsp is None:
                                continue
                            else:
                                # data finished.. keep connection open though
                                self.logger.debug('incoming message finished')
                                return True

                        self.socket.send(force_bString(rsp + "\r\n"))
                        if keep == 0:
                            self.closeconn()
                            self.finish_outgoing_connection()
                            return False
                else:
                    # EOF
                    return False
コード例 #13
0
ファイル: sa.py プロジェクト: danBLA/fuglu
    def safilter(self, messagecontent, user):
        """pass content to sa, return sa-processed mail"""
        retries = self.config.getint(self.section, 'retries')
        peruserconfig = self.config.getboolean(self.section, 'peruserconfig')
        spamsize = len(messagecontent)
        for i in range(0, retries):
            try:
                self.logger.debug('Contacting spamd (Try %s of %s)' % (i + 1, retries))
                s = self.__init_socket()
                s.sendall(force_bString('PROCESS SPAMC/1.2'))
                s.sendall(force_bString("\r\n"))
                s.sendall(force_bString("Content-length: %s" % spamsize))
                s.sendall(force_bString("\r\n"))
                if peruserconfig:
                    s.sendall(force_bString("User: %s" % user))
                    s.sendall(force_bString("\r\n"))
                s.sendall(force_bString("\r\n"))
                s.sendall(force_bString(messagecontent))
                self.logger.debug('Sent %s bytes to spamd' % spamsize)
                s.shutdown(socket.SHUT_WR)
                socketfile = s.makefile("rb")
                line1_info = socketfile.readline()
                line1_info = force_uString(line1_info)  # convert to unicode string
                self.logger.debug(line1_info)
                line2_contentlength = socketfile.readline()
                line3_empty = socketfile.readline()
                content = socketfile.read()
                self.logger.debug('Got %s message bytes from back from spamd' % len(content))
                answer = line1_info.strip().split()
                if len(answer) != 3:
                    self.logger.error("Got invalid status line from spamd: %s" % line1_info)
                    continue

                version, number, status = answer
                if status != 'EX_OK':
                    self.logger.error("Got bad status from spamd: %s" % status)
                    continue

                return content
            except socket.timeout:
                self.logger.error('SPAMD Socket timed out.')
            except socket.herror as h:
                self.logger.error('SPAMD Herror encountered : %s' % str(h))
            except socket.gaierror as g:
                self.logger.error('SPAMD gaierror encountered: %s' % str(g))
            except socket.error as e:
                self.logger.error('SPAMD socket error: %s' % str(e))
            except Exception as e:
                self.logger.error('SPAMD communication error: %s' % str(e))

            time.sleep(1)
        return None
コード例 #14
0
def buildmsgsource(suspect):
    """Build the message source with fuglu headers prepended"""

    # we must prepend headers manually as we can't set a header order in email
    # objects

    # -> the original message source is bytes
    origmsgtxt = suspect.get_source()
    newheaders = ""

    for key in suspect.addheaders:
        # is ignore the right thing to do here?
        val = suspect.addheaders[key]
        #self.logger.debug('Adding header %s : %s'%(key,val))
        hdr = Header(val, header_name=key, continuation_ws=' ')
        newheaders += "%s: %s\r\n" % (key, hdr.encode())

    # the original message should be in bytes, make sure the header added
    # is an encoded string as well
    modifiedtext = force_bString(newheaders) + force_bString(origmsgtxt)
    return modifiedtext
コード例 #15
0
 def lint_ping(self):
     try:
         s = self.__init_socket__(oneshot=True)
     except Exception as e:
         print("Could not contact clamd: %s" % (str(e)))
         return False
     s.sendall(force_bString('PING'))
     result = s.recv(20000)
     print("Got Pong: %s" % force_uString(result))
     if result.strip() != b'PONG':
         print("Invalid PONG: %s" % force_uString(result))
     return True
コード例 #16
0
    def scan_file(self, filename):
        filename = os.path.abspath(filename)
        s = self.__init_socket__()
        s.sendall(force_bString('SCAN %s FILE %s' % (self.config.get(self.section, 'scanoptions'), filename)))
        s.sendall(b'\n')

        result = s.recv(20000)
        if len(result) < 1:
            self.logger.error('Got no reply from fpscand')
        s.close()

        return self._parse_result(result)
コード例 #17
0
    def lint_file(self):
        import tempfile
        (handle, tempfilename) = tempfile.mkstemp(prefix='fuglu', dir=self.config.get('main', 'tempdir'))
        tempfilename = tempfilename

        stream = """Date: Mon, 08 Sep 2008 17:33:54 +0200
To: [email protected]
From: [email protected]
Subject: test eicar attachment
X-Mailer: swaks v20061116.0 jetmore.org/john/code/#swaks
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="----=_MIME_BOUNDARY_000_12140"

------=_MIME_BOUNDARY_000_12140
Content-Type: text/plain

Eicar test
------=_MIME_BOUNDARY_000_12140
Content-Type: application/octet-stream
Content-Transfer-Encoding: BASE64
Content-Disposition: attachment

UEsDBAoAAAAAAGQ7WyUjS4psRgAAAEYAAAAJAAAAZWljYXIuY29tWDVPIVAlQEFQWzRcUFpYNTQo
UF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCoNClBLAQIU
AAoAAAAAAGQ7WyUjS4psRgAAAEYAAAAJAAAAAAAAAAEAIAD/gQAAAABlaWNhci5jb21QSwUGAAAA
AAEAAQA3AAAAbQAAAAAA

------=_MIME_BOUNDARY_000_12140--"""
        with os.fdopen(handle, 'w+b') as fd:
            fd.write(force_bString(stream))

        try:
            viruses = self.scan_file(tempfilename)
        except Exception as e:
            print(e)
            return False

        try:
            os.remove(tempfilename)
        except Exception:
            pass

        try:
            for fname, virus in iter(viruses.items()):
                print("F-Prot AV (file mode): Found virus: %s in %s" % (virus, fname))
                if "EICAR" in virus:
                    return True
        except Exception as e:
            print(e)
            return False

        print("Couldn't find EICAR in tmp file: %s" % fname)
        return False
コード例 #18
0
ファイル: endtoend_test.py プロジェクト: danBLA/fuglu
    def test_reinject_error(self):
        """test if a reinject error is passed"""

        # give fuglu time to start listener
        time.sleep(1)

        import logging
        import sys

        root = logging.getLogger()
        root.setLevel(logging.DEBUG)
        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.DEBUG)
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        ch.setFormatter(formatter)
        root.addHandler(ch)


        # send test message
        smtpclient = smtplib.SMTP('127.0.0.1', ReinjectErrorTestCase.FUGLU_PORT)
        # smtpServer.set_debuglevel(1)
        (code, msg) = smtpclient.helo('test.e2e')

        self.assertEqual(250, code)


        testmessage = u"""Hello World!"""

        # TODO: this test fails if we don't put in the \r in there... (eg,
        # fuglu adds it) - is this a bug or wrong test?

        msg = MIMEText(testmessage)
        msg["Subject"] = "End to End Test"
        msgstring = msg.as_string()
        inbytes = len(msg.get_payload(decode=True))
        # envelope sender/recipients
        env_sender = u'*****@*****.**'
        env_recipients = [u'*****@*****.**']

        self.smtp.response_code = 554
        # python 3 returnes bytes
        self.smtp.response_message = '5.4.0 Error: too many hops'

        try:
            smtpclient.sendmail(force_uString(env_sender),
                                force_uString(env_recipients),
                                force_bString(msgstring))
        except smtplib.SMTPDataError as e:
            self.assertEqual(self.smtp.response_code, e.smtp_code)
            self.assertEqual(self.smtp.response_message, force_uString(e.smtp_error))
        pass
コード例 #19
0
 def add_rcpt(self, rcpt):
     """
     Add a new envelope recipient
     Args:
         rcpt (str, unicode): new recipient mail address, with <> qualification
     """
     if not self.sess.has_option(lm.SMFIF_ADDRCPT_PAR):
         self.logger.error(
             'Add rcpt called without the proper opts set, '
             'availability -> fuglu: %s, mta: %s' %
             (self.sess.has_option(lm.SMFIF_ADDRCPT_PAR, client="fuglu"),
              self.sess.has_option(lm.SMFIF_ADDRCPT_PAR, client="mta")))
         return
     self.sess.addRcpt(force_bString(rcpt))
コード例 #20
0
    def _create_hash(self, value):
        hashtype = self.config.get(self.section, 'hash').lower()

        if hasattr(hashlib, 'algorithms_guaranteed'):
            algorithms = hashlib.algorithms_guaranteed
        else:
            algorithms = ['md5', 'sha1']  #python 2.6

        if hashtype in algorithms:
            hasher = getattr(hashlib, hashtype)
            myhash = hasher(force_bString(value)).hexdigest()
        else:
            myhash = ''
        return myhash
コード例 #21
0
 def change_from(self, from_address):
     """
     Change envelope from mail address.
     Args:
         from_address (unicode,str): new from mail address
     """
     if not self.sess.has_option(lm.SMFIF_CHGFROM):
         self.logger.error(
             'Change from called without the proper opts set, '
             'availability -> fuglu: %s, mta: %s' %
             (self.sess.has_option(lm.SMFIF_CHGFROM, client="fuglu"),
              self.sess.has_option(lm.SMFIF_CHGFROM, client="mta")))
         return
     self.sess.chgFrom(force_bString(from_address))
コード例 #22
0
ファイル: esmtpconnector.py プロジェクト: danBLA/fuglu
    def re_inject(self, suspect):
        """Send message back to postfix"""
        if suspect.get_tag('noreinject'):
            # in esmtp sessions we don't want to provide info to the connecting
            # client
            return 250, 'OK'
        if suspect.get_tag('reinjectoriginal'):
            self.logger.info(
                'Injecting original message source without modifications')
            msgcontent = suspect.get_original_source()
        else:
            msgcontent = buildmsgsource(suspect)

        code, answer = self.sess.forwardconn.data(force_bString(msgcontent))
        answer = force_uString(answer)
        return code, answer
コード例 #23
0
    def replacebody(self, newbody):
        """
        Replace message body sending corresponding command to MTA
        using protocol stored in self.sess

        Args:
            newbody (string(encoded)): new message body
        """
        # check if option is available
        if not self.sess.has_option(lm.SMFIF_CHGBODY):
            self.logger.error(
                'Change body called without the proper opts set, '
                'availability -> fuglu: %s, mta: %s' %
                (self.sess.has_option(lm.SMFIF_CHGBODY, client="fuglu"),
                 self.sess.has_option(lm.SMFIF_CHGBODY, client="mta")))
            return
        self.sess.replBody(force_bString(newbody))
コード例 #24
0
    def scan_shell(self, content):
        clamscan = self.config.get(self.section, 'clamscan')
        timeout = self.config.getint(self.section, 'clamscantimeout')

        if not os.path.exists(clamscan):
            raise Exception('could not find clamscan executable in %s' %
                            clamscan)

        try:
            process = subprocess.Popen(
                [clamscan, u'-'],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE)  # file data by pipe
            kill_proc = lambda p: p.kill()
            timer = threading.Timer(timeout, kill_proc, [process])
            timer.start()
            stdout = process.communicate(force_bString(content))[0]
            process.stdin.close()
            exitcode = process.wait()
            timer.cancel()
        except Exception:
            exitcode = -1
            stdout = ''

        if exitcode > 1:  # 0: no virus, 1: virus, >1: error, -1 subprocess error
            raise Exception('clamscan error')
        elif exitcode < 0:
            raise Exception('clamscan timeout after %ss' % timeout)

        dr = {}

        for line in stdout.splitlines():
            line = line.strip()
            if line.endswith(b'FOUND'):
                filename, virusname, found = line.rsplit(None, 2)
                filename = force_uString(filename.rstrip(b':'))
                dr[filename] = virusname

        if dr == {}:
            return None
        else:
            return dr
コード例 #25
0
ファイル: a_statsd.py プロジェクト: danBLA/fuglu
    def process(self, suspect, decision):
        buffer = "%s.fuglu.decision.%s:1|c\n" % (
            self.nodename, actioncode_to_string(decision))

        host = self.config.get(self.section, 'host')
        port = self.config.getint(self.section, 'port')

        if self.sock is None:
            addr_f = socket.getaddrinfo(host, 0)[0][0]
            self.sock = socket.socket(addr_f, socket.SOCK_DGRAM)

        if suspect.is_virus():
            buffer = "%s%s.fuglu.message.virus:1|c\n" % (buffer, self.nodename)
        elif suspect.is_highspam():
            buffer = "%s%s.fuglu.message.highspam:1|c\n" % (buffer,
                                                            self.nodename)
        elif suspect.is_spam():
            buffer = "%s%s.fuglu.message.spam:1|c\n" % (buffer, self.nodename)
        else:
            buffer = "%s%s.fuglu.message.clean:1|c\n" % (buffer, self.nodename)

        self.sock.sendto(force_bString(buffer), (host, port))
コード例 #26
0
    def _parse_result(self, result):
        dr = {}
        result = force_uString(result)
        for line in result.strip().split('\n'):
            m = self.pattern.match(force_bString(line))
            if m is None:
                self.logger.error('Could not parse line from f-prot: %s' % line)
                raise Exception('f-prot: Unparseable answer: %s' % result)
            status = force_uString(m.group(1))
            text = force_uString(m.group(2))
            details = force_uString(m.group(3))

            status = int(status)
            self.logger.debug("f-prot scan status: %s" % status)
            self.logger.debug("f-prot scan text: %s" % text)
            if status == 0:
                continue

            if status > 3:
                self.logger.warning("f-prot: got unusual status %s (result: %s)" % (status, result))

            # http://www.f-prot.com/support/helpfiles/unix/appendix_c.html
            if status & 1 == 1 or status & 2 == 2:
                # we have a infection
                if text[0:10] == "infected: ":
                    text = text[10:]
                elif text[0:27] == "contains infected objects: ":
                    text = text[27:]
                else:
                    self.logger.warn("Unexpected reply from f-prot: %s" % text)
                    continue
                dr[details] = text

        if len(dr) == 0:
            return None
        else:
            return dr
コード例 #27
0
ファイル: bounce.py プロジェクト: danBLA/fuglu
    def _add_required_headers(self, recipient, messagecontent):
        """add headers required for sending automated mail"""

        msgrep = email.message_from_bytes(force_bString(messagecontent))
        msgrep.set_charset(
            "utf-8")  # define unicode because the messagecontent is unicode

        if not 'to' in msgrep:
            msgrep['To'] = Header("<%s>" % recipient).encode()

        if not 'From' in msgrep:
            msgrep['from'] = Header("<MAILER-DAEMON@%s>" %
                                    socket.gethostname()).encode()

        if not 'auto-submitted' in msgrep:
            msgrep['auto-submitted'] = Header('auto-generated').encode()

        if not 'date' in msgrep:
            msgrep['Date'] = formatdate(localtime=True)

        if not 'Message-id' in msgrep:
            msgrep['Message-ID'] = make_msgid()

        return msgrep.as_string()
コード例 #28
0
ファイル: ncconnector.py プロジェクト: danBLA/fuglu
    def getincomingmail(self):
        """return true if mail got in, false on error Session will be kept open"""
        self.socket.send(
            force_bString(
                "fuglu scanner ready - please pipe your message, "
                "(optional) include env sender/recipient in the beginning, "
                "see documentation\r\n"))
        try:
            (handle, tempfilename) = tempfile.mkstemp(prefix='fuglu',
                                                      dir=self.config.get(
                                                          'main', 'tempdir'))
            self.tempfilename = tempfilename
            self.tempfile = os.fdopen(handle, 'w+b')
        except Exception as e:
            self.endsession('could not write to tempfile')

        collect_lumps = []
        while True:
            data = self.socket.recv(1024)
            if len(data) < 1:
                break
            else:
                collect_lumps.append(data)

        data = b"".join(collect_lumps)

        data = self.parse_remove_env_data(data)

        self.tempfile.write(data)
        self.tempfile.close()
        if not data:
            self.logger.debug('Problem receiving or parsing message')
            return False
        else:
            self.logger.debug('Incoming message received')
        return True
コード例 #29
0
    def scan_stream(self, content, suspectid="(NA)"):
        """
        Scan byte buffer

        return either :
          - (dict) : {filename1: "virusname"}
          - None if no virus found
          - raises Exception if something went wrong
        """
        pipelining = self.config.getboolean(self.section, 'pipelining')
        s = self.__init_socket__(oneshot=not pipelining)
        s.sendall(b'zINSTREAM\0')
        default_chunk_size = 2048
        remainingbytes = force_bString(content)

        numChunksToSend = math.ceil(len(remainingbytes) / default_chunk_size)
        iChunk = 0
        chunklength = 0
        self.logger.debug('%s: sending message in %u chunks of size %u bytes' %
                          (suspectid, numChunksToSend, default_chunk_size))

        while len(remainingbytes) > 0:
            iChunk = iChunk + 1
            chunklength = min(default_chunk_size, len(remainingbytes))
            #self.logger.debug('sending chunk %u/%u' % (iChunk,numChunksToSend))
            #self.logger.debug('sending %s byte chunk' % chunklength)
            chunkdata = remainingbytes[:chunklength]
            remainingbytes = remainingbytes[chunklength:]
            s.sendall(struct.pack(b'!L', chunklength))
            s.sendall(chunkdata)
        self.logger.debug(
            '%s: sent chunk %u/%u, last number of bytes sent was %u' %
            (suspectid, iChunk, numChunksToSend, chunklength))
        self.logger.debug(
            '%s: All chunks send, send 0 - size to tell ClamAV the whole message has been sent'
            % suspectid)
        s.sendall(struct.pack(b'!L', 0))
        dr = {}

        result = force_uString(self._read_until_delimiter(s,
                                                          suspectid)).strip()

        if result.startswith('INSTREAM size limit exceeded'):
            raise Exception(
                "%s: Clamd size limit exeeded. Make sure fuglu's clamd maxsize config is not larger than clamd's StreamMaxLength"
                % suspectid)
        if result.startswith('UNKNOWN'):
            raise Exception(
                "%s: Clamd doesn't understand INSTREAM command. very old version?"
                % suspectid)

        if pipelining:
            try:
                ans_id, filename, virusinfo = result.split(':', 2)
                filename = force_uString(
                    filename.strip())  # use unicode for filename
                virusinfo = force_uString(
                    virusinfo.strip())  # lets use unicode for the info
            except Exception:
                raise Exception(
                    "%s: Protocol error, could not parse result: %s" %
                    (suspectid, result))

            threadLocal.expectedID += 1
            if threadLocal.expectedID != int(ans_id):
                raise Exception(
                    "Commands out of sync - expected ID %s - got %s" %
                    (threadLocal.expectedID, ans_id))

            if virusinfo[-5:] == 'ERROR':
                raise Exception(virusinfo)
            elif virusinfo != 'OK':
                dr[filename] = virusinfo.replace(" FOUND", '')

            if threadLocal.expectedID >= MAX_SCANS_PER_SOCKET:
                try:
                    s.sendall(b'zEND\0')
                    s.close()
                finally:
                    self.__invalidate_socket()
        else:
            filename, virusinfo = result.split(':', 1)
            filename = force_uString(
                filename.strip())  # use unicode for filename
            virusinfo = force_uString(
                virusinfo.strip())  # use unicode for virus info
            if virusinfo[-5:] == 'ERROR':
                raise Exception(virusinfo)
            elif virusinfo != 'OK':
                dr[filename] = virusinfo.replace(" FOUND", '')
            s.close()

        if dr == {}:
            return None
        else:
            return dr
コード例 #30
0
ファイル: patcheddkimlib.py プロジェクト: danBLA/fuglu
def verify(message, debuglog=None):
    """Verify a DKIM signature on an RFC822 formatted message.

    @param message: an RFC822 formatted message (with either \\n or \\r\\n line endings)
    @param debuglog: a file-like object to which debug info will be written (default None)

    """

    (headers, body) = rfc822_parse(message)

    sigheaders = [x for x in headers if x[0].lower() == "dkim-signature"]
    if len(sigheaders) < 1:
        return False

    # Currently, we only validate the first DKIM-Signature line found.

    a = re.split(r"\s*;\s*", sigheaders[0][1].strip())
    if debuglog is not None:
        print("a:", a, file=debuglog)
    sig = {}
    for x in a:
        if x:
            m = re.match(r"(\w+)\s*=\s*(.*)", x, re.DOTALL)
            if m is None:
                if debuglog is not None:
                    print("invalid format of signature part: %s" % x,
                          file=debuglog)
                return False
            sig[m.group(1)] = m.group(2)
    if debuglog is not None:
        print("sig:", sig, file=debuglog)

    if 'v' not in sig:
        if debuglog is not None:
            print("signature missing v=", file=debuglog)
        return False
    if sig['v'] != "1":
        if debuglog is not None:
            print("v= value is not 1 (%s)" % sig['v'], file=debuglog)
        return False
    if 'a' not in sig:
        if debuglog is not None:
            print("signature missing a=", file=debuglog)
        return False
    if 'b' not in sig:
        if debuglog is not None:
            print("signature missing b=", file=debuglog)
        return False
    if re.match(r"[\s0-9A-Za-z+/]+=*$", sig['b']) is None:
        if debuglog is not None:
            print("b= value is not valid base64 (%s)" % sig['b'],
                  file=debuglog)
        return False
    if 'bh' not in sig:
        if debuglog is not None:
            print("signature missing bh=", file=debuglog)
        return False
    if re.match(r"[\s0-9A-Za-z+/]+=*$", sig['bh']) is None:
        if debuglog is not None:
            print("bh= value is not valid base64 (%s)" % sig['bh'],
                  file=debuglog)
        return False
    if 'd' not in sig:
        if debuglog is not None:
            print("signature missing d=", file=debuglog)
        return False
    if 'h' not in sig:
        if debuglog is not None:
            print("signature missing h=", file=debuglog)
        return False
    if 'i' in sig and (not sig['i'].endswith(sig['d'])
                       or sig['i'][-len(sig['d']) - 1] not in "@."):
        if debuglog is not None:
            print("i= domain is not a subdomain of d= (i=%s d=%d)" %
                  (sig['i'], sig['d']),
                  file=debuglog)
        return False
    if 'l' in sig and re.match(r"\d{,76}$", sig['l']) is None:
        if debuglog is not None:
            print("l= value is not a decimal integer (%s)" % sig['l'],
                  file=debuglog)
        return False
    if 'q' in sig and sig['q'] != "dns/txt":
        if debuglog is not None:
            print("q= value is not dns/txt (%s)" % sig['q'], file=debuglog)
        return False
    if 's' not in sig:
        if debuglog is not None:
            print("signature missing s=", file=debuglog)
        return False
    if 't' in sig and re.match(r"\d+$", sig['t']) is None:
        if debuglog is not None:
            print("t= value is not a decimal integer (%s)" % sig['t'],
                  file=debuglog)
        return False
    if 'x' in sig:
        if re.match(r"\d+$", sig['x']) is None:
            if debuglog is not None:
                print("x= value is not a decimal integer (%s)" % sig['x'],
                      file=debuglog)
            return False
        if int(sig['x']) < int(sig['t']):
            if debuglog is not None:
                print("x= value is less than t= value (x=%s t=%s)" %
                      (sig['x'], sig['t']),
                      file=debuglog)
            return False

    m = re.match("(\w+)(?:/(\w+))?$", sig['c'])
    if m is None:
        if debuglog is not None:
            print("c= value is not in format method/method (%s)" % sig['c'],
                  file=debuglog)
        return False
    can_headers = m.group(1)
    if m.group(2) is not None:
        can_body = m.group(2)
    else:
        can_body = "simple"

    if can_headers == "simple":
        canonicalize_headers = Simple
    elif can_headers == "relaxed":
        canonicalize_headers = Relaxed
    else:
        if debuglog is not None:
            print("Unknown header canonicalization (%s)" % can_headers,
                  file=debuglog)
        return False

    headers = canonicalize_headers.canonicalize_headers(headers)

    if can_body == "simple":
        body = Simple.canonicalize_body(body)
    elif can_body == "relaxed":
        body = Relaxed.canonicalize_body(body)
    else:
        if debuglog is not None:
            print("Unknown body canonicalization (%s)" % can_body,
                  file=debuglog)
        return False

    if sig['a'] == "rsa-sha1":
        hasher = hashlib.sha1
        hashid = HASHID_SHA1
    elif sig['a'] == "rsa-sha256":
        hasher = hashlib.sha256
        hashid = HASHID_SHA256
    else:
        if debuglog is not None:
            print("Unknown signature algorithm (%s)" % sig['a'], file=debuglog)
        return False

    if 'l' in sig:
        body = body[:int(sig['l'])]

    h = hasher()
    h.update(force_bString(body))
    bodyhash = h.digest()
    if debuglog is not None:
        print("bh:", base64.b64encode(bodyhash), file=debuglog)
    if bodyhash != base64.b64decode(re.sub(r"\s+", "", sig['bh'])):
        if debuglog is not None:
            print("body hash mismatch (got %s, expected %s)" %
                  (base64.b64encode(bodyhash), sig['bh']),
                  file=debuglog)
        return False

    s = dnstxt(sig['s'] + "._domainkey." + sig['d'] + ".")
    if not s:
        return False
    a = re.split(r"\s*;\s*", s)
    pub = {}
    for f in a:
        m = re.match(r"(\w+)=(.*)", f)
        if m is not None:
            pub[m.group(1)] = m.group(2)
        else:
            if debuglog is not None:
                print("invalid format in _domainkey txt record", file=debuglog)
            return False
    pkey = base64.b64decode(pub['p'])
    pkey = force_cfromb(pkey)

    x = asn1_parse(ASN1_Object, pkey)
    # Not sure why the [1:] is necessary to skip a byte.
    pkd = asn1_parse(ASN1_RSAPublicKey, x[0][1][1:])
    pk = {
        'modulus': pkd[0][0],
        'publicExponent': pkd[0][1],
    }
    modlen = len(int2str(pk['modulus']))
    if debuglog is not None:
        print("modlen:", modlen, file=debuglog)

    include_headers = re.split(r"\s*:\s*", sig['h'])
    if debuglog is not None:
        print("include_headers:", include_headers, file=debuglog)
    sign_headers = []
    lastindex = {}
    for h in include_headers:
        i = lastindex.get(h, len(headers))
        while i > 0:
            i -= 1
            if h.lower() == headers[i][0].lower():
                sign_headers.append(headers[i])
                break
        lastindex[h] = i
    # The call to _remove() assumes that the signature b= only appears once in
    # the signature header
    sign_headers += [
        (x[0], x[1].rstrip())
        for x in canonicalize_headers.canonicalize_headers([(
            sigheaders[0][0], _remove(sigheaders[0][1], sig['b']))])
    ]
    if debuglog is not None:
        print("verify headers:", sign_headers, file=debuglog)

    h = hasher()
    for x in sign_headers:
        h.update(force_bString(x[0]))
        h.update(force_bString(":"))
        h.update(force_bString(x[1]))
    d = h.digest()
    d = force_cfromb(d)

    if debuglog is not None:
        print("verify digest:",
              " ".join("%02x" % ord(x) for x in d),
              file=debuglog)

    dinfo = asn1_build((SEQUENCE, [
        (SEQUENCE, [
            (OBJECT_IDENTIFIER, hashid),
            (NULL, None),
        ]),
        (OCTET_STRING, d),
    ]))
    if debuglog is not None:
        print("dinfo:",
              " ".join("%02x" % ord(x) for x in dinfo),
              file=debuglog)
    if len(dinfo) + 3 > modlen:
        if debuglog is not None:
            print("Hash too large for modulus", file=debuglog)
        return False
    sig2 = "\x00\x01" + "\xff" * (modlen - len(dinfo) - 3) + "\x00" + dinfo
    sig2 = force_cfromb(sig2)
    if debuglog is not None:
        print("sig2:", " ".join("%02x" % ord(x) for x in sig2), file=debuglog)
        print(sig['b'], file=debuglog)
        print(re.sub(r"\s+", "", sig['b']), file=debuglog)

    sigEncoded = base64.b64decode(force_bfromc(re.sub(r"\s+", "", sig['b'])))
    sigEncoded = force_cfromb((sigEncoded))

    v = int2str(pow(str2int(sigEncoded), pk['publicExponent'], pk['modulus']),
                modlen)

    if debuglog is not None:
        print("v:", " ".join("%02x" % ord(x) for x in v), file=debuglog)
    assert len(v) == len(sig2)
    # Byte-by-byte compare of signatures
    return not [1 for x in zip(v, sig2) if x[0] != x[1]]