⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 sb_server.py

📁 用python实现的邮件过滤器
💻 PY
📖 第 1 页 / 共 3 页
字号:
            y = 1/x        self.serverSocket.push(self.request + '\r\n')        if self.request.strip() == '':            # Someone just hit the Enter key.            self.command = ''            self.args = []        else:            # A proper command.            splitCommand = self.request.strip().split()            self.command = splitCommand[0].upper()            self.args = splitCommand[1:]            self.startTime = time.time()        self.request = ''    def onResponse(self):        # There are some features, tested by clients using CAPA,        # that we don't support.  We strip them from the CAPA        # response here, so that the client won't use them.        for unsupported in ['PIPELINING', 'STLS', ]:            unsupportedLine = r'(?im)^%s[^\n]*\n' % (unsupported,)            self.response = re.sub(unsupportedLine, '', self.response)        # Pass the request and the raw response to the subclass and        # send back the cooked response.        if self.response:            cooked = self.onTransaction(self.command, self.args, self.response)            self.push(cooked)        # If onServerLine() decided that the server has closed its        # socket, close this one when the response has been sent.        if self.isClosing:            self.close_when_done()        # Reset.        self.command = ''        self.args = []        self.isClosing = False        self.seenAllHeaders = Falseclass BayesProxyListener(Dibbler.Listener):    """Listens for incoming email client connections and spins off    BayesProxy objects to serve them.    """    def __init__(self, serverName, serverPort, proxyPort, ssl=False):        proxyArgs = (serverName, serverPort, ssl)        Dibbler.Listener.__init__(self, proxyPort, BayesProxy, proxyArgs)        print 'Listener on port %s is proxying %s:%d' % \               (_addressPortStr(proxyPort), serverName, serverPort)class BayesProxy(POP3ProxyBase):    """Proxies between an email client and a POP3 server, inserting    judgement headers.  It acts on the following POP3 commands:     o STAT:        o Adds the size of all the judgement headers to the maildrop          size.     o LIST:        o With no message number: adds the size of an judgement header          to the message size for each message in the scan listing.        o With a message number: adds the size of an judgement header          to the message size.     o RETR:        o Adds the judgement header based on the raw headers and body          of the message.     o TOP:        o Adds the judgement header based on the raw headers and as          much of the body as the TOP command retrieves.  This can          mean that the header might have a different value for          different calls to TOP, or for calls to TOP vs. calls to          RETR.  I'm assuming that the email client will either not          make multiple calls, or will cope with the headers being          different.     o USER:        o Does no processing based on the USER command itself, but          expires any old messages in the three caches.    """    def __init__(self, clientSocket, serverName, serverPort, ssl=False):        POP3ProxyBase.__init__(self, clientSocket, serverName, serverPort,                               ssl)        self.handlers = {'STAT': self.onStat, 'LIST': self.onList,                         'RETR': self.onRetr, 'TOP': self.onTop,                         'USER': self.onUser}        state.totalSessions += 1        state.activeSessions += 1        self.isClosed = False    def send(self, data):        """Logs the data to the log file."""        if options["globals", "verbose"]:            state.logFile.write(data)            state.logFile.flush()        try:            return POP3ProxyBase.send(self, data)        except socket.error:            # The email client has closed the connection - 40tude Dialog            # does this immediately after issuing a QUIT command,            # without waiting for the response.            self.close()    def recv(self, size):        """Logs the data to the log file."""        data = POP3ProxyBase.recv(self, size)        if options["globals", "verbose"]:            state.logFile.write(data)            state.logFile.flush()        return data    def close(self):        # This can be called multiple times by async.        if not self.isClosed:            self.isClosed = True            state.activeSessions -= 1            POP3ProxyBase.close(self)    def onTransaction(self, command, args, response):        """Takes the raw request and response, and returns the        (possibly processed) response to pass back to the email client.        """        handler = self.handlers.get(command, self.onUnknown)        return handler(command, args, response)    def onStat(self, command, args, response):        """Adds the size of all the judgement headers to the maildrop        size."""        match = re.search(r'^\+OK\s+(\d+)\s+(\d+)(.*)\r\n', response)        if match:            count = int(match.group(1))            size = int(match.group(2)) + HEADER_SIZE_FUDGE_FACTOR * count            return '+OK %d %d%s\r\n' % (count, size, match.group(3))        else:            return response    def onList(self, command, args, response):        """Adds the size of an judgement header to the message        size(s)."""        if response.count('\r\n') > 1:            # Multiline: all lines but the first contain a message size.            lines = response.split('\r\n')            outputLines = [lines[0]]            for line in lines[1:]:                match = re.search(r'^(\d+)\s+(\d+)', line)                if match:                    number = int(match.group(1))                    size = int(match.group(2)) + HEADER_SIZE_FUDGE_FACTOR                    line = "%d %d" % (number, size)                outputLines.append(line)            return '\r\n'.join(outputLines)        else:            # Single line.            match = re.search(r'^\+OK\s+(\d+)\s+(\d+)(.*)\r\n', response)            if match:                messageNumber = match.group(1)                size = int(match.group(2)) + HEADER_SIZE_FUDGE_FACTOR                trailer = match.group(3)                return "+OK %s %s%s\r\n" % (messageNumber, size, trailer)            else:                return response    def onRetr(self, command, args, response):        """Adds the judgement header based on the raw headers and body        of the message."""        # Previously, we used '\n\r?\n' to detect the end of the headers in        # case of broken emails that don't use the proper line separators,        # and if we couldn't find it, then we assumed that the response was        # and error response and passed it unfiltered.  However, if the        # message doesn't contain the separator (malformed mail), then this        # would mean the message was passed straight through the proxy.        # Since all the content is then in the headers, this probably        # doesn't do a spammer much good, but, just in case, we now just        # check for "+OK" and assume no error response will be given if        # that is (which seems reasonable).        # Remove the trailing .\r\n before passing to the email parser.        # Thanks to Scott Schlesier for this fix.        terminatingDotPresent = (response[-4:] == '\n.\r\n')        if terminatingDotPresent:            response = response[:-3]        # Break off the first line, which will be '+OK'.        statusLine, messageText = response.split('\n', 1)        statusData = statusLine.split()        ok = statusData[0]        if ok.strip().upper() != "+OK":            # Must be an error response.  Return unproxied.            return response        try:            msg = email.message_from_string(messageText,                      _class=spambayes.message.SBHeaderMessage)            msg.setId(state.getNewMessageName())            # Now find the spam disposition and add the header.            (prob, clues) = state.bayes.spamprob(msg.tokenize(),\                             evidence=True)            msg.addSBHeaders(prob, clues)            # Check for "RETR" or "TOP N 99999999" - fetchmail without            # the 'fetchall' option uses the latter to retrieve messages.            if (command == 'RETR' or                (command == 'TOP' and                 len(args) == 2 and args[1] == '99999999')):                cls = msg.GetClassification()                state.RecordClassification(cls, prob)                # Suppress caching of "Precedence: bulk" or                # "Precedence: list" ham if the options say so.                isSuppressedBulkHam = \                    (cls == options["Headers", "header_ham_string"] and                     options["Storage", "no_cache_bulk_ham"] and                     msg.get('precedence') in ['bulk', 'list'])                # Suppress large messages if the options say so.                size_limit = options["Storage",                                     "no_cache_large_messages"]                isTooBig = size_limit > 0 and \                           len(messageText) > size_limit                # Cache the message.  Don't pollute the cache with test                # messages or suppressed bulk ham.                if (not state.isTest and                    options["Storage", "cache_messages"] and                    not isSuppressedBulkHam and not isTooBig):                    # Write the message into the Unknown cache.                    makeMessage = state.unknownCorpus.makeMessage                    message = makeMessage(msg.getId(), msg.as_string())                    state.unknownCorpus.addMessage(message)            # We'll return the message with the headers added.  We take            # all the headers from the SBHeaderMessage, but take the body            # directly from the POP3 conversation, because the            # SBHeaderMessage might have "fixed" a partial message by            # appending a closing boundary separator.  Remember we can            # be dealing with partial message here because of the timeout            # code in onServerLine.            headers = []            for name, value in msg.items():                header = "%s: %s" % (name, value)                headers.append(re.sub(r'\r?\n', '\r\n', header))            try:                body = re.split(r'\n\r?\n', messageText, 1)[1]            except IndexError:                # No separator, so no body.  Bad message, but proxy it                # through anyway (adding the missing separator).                messageText = "\r\n".join(headers) + "\r\n\r\n"            else:                messageText = "\r\n".join(headers) + "\r\n\r\n" + body        except:            # Something nasty happened while parsing or classifying -            # report the exception in a hand-appended header and recover.            # This is one case where an unqualified 'except' is OK, 'cos            # anything's better than destroying people's email...            messageText, details = spambayes.message.\                                   insert_exception_header(messageText)            # Print the exception and a traceback.            print >>sys.stderr, details        # Restore the +OK and the POP3 .\r\n terminator if there was one.        retval = ok + "\n" + messageText        if terminatingDotPresent:            retval += '.\r\n'        return retval    def onTop(self, command, args, response):        """Adds the judgement header based on the raw headers and as        much of the body as the TOP command retrieves."""        # Easy (but see the caveat in BayesProxy.__doc__).        return self.onRetr(command, args, response)    def onUser(self, command, args, response):        """Spins off three separate threads that expires any old messages        in the three caches, but does not do any processing of the USER        command itself."""        start_new_thread(state.spamCorpus.removeExpiredMessages, ())        start_new_thread(state.hamCorpus.removeExpiredMessages, ())        start_new_thread(state.unknownCorpus.removeExpiredMessages, ())        return response    def onUnknown(self, command, args, response):        """Default handler; returns the server's response verbatim."""        return response# Implementations of a mutex or other resource which can prevent# multiple servers starting at once.  Platform specific as no reasonable# cross-platform solution exists (however, an old trick is to use a# directory for a mutex, as a "create/test" atomic API generally exists).# Will return a handle to be later closed, or may throw AlreadyRunningExceptiondef open_platform_mutex(mutex_name="SpamBayesServer"):    if sys.platform.startswith("win"):        try:            import win32event, win32api, winerror, win32con            import pywintypes, ntsecuritycon            # ideally, the mutex name could include either the username,            # or the munged path to the INI file - this would mean we            # would allow multiple starts so long as they weren't for            # the same user.  However, as of now, the service version            # is likely to start as a different user, so a single mutex            # is best for now.            # XXX - even if we do get clever with another mutex name, we            # should consider still creating a non-exclusive            # "SpamBayesServer" mutex, if for no better reason than so            # an installer can check if we are running            try:                hmutex = win32event.CreateMutex(None, True, mutex_name)            except win32event.error, details:                # If another user has the mutex open, we get an "access denied"                # error - this is still telling us what we need to know.                if details[0] != winerror.ERROR_ACCESS_DENIED:                    raise                raise AlreadyRunningException            # mutex opened - now check if we actually created it.            if win32api.GetLastError()==winerror.ERROR_ALREADY_EXISTS:                win32api.CloseHandle(hmutex)                raise AlreadyRunningException            return hmutex        except ImportError:            # no win32all - no worries, just start            pass    return Nonedef close_platform_mutex(mutex):    if sys.platform.startswith("win"):        if mutex is not None:            mutex.Close()# This keeps the global state of the module - the command-line options,# statistics like how many mails have been classified, the handle of the# log file, the Classifier and FileCorpus objects, and so on.class State:    def __init__(self):        """Initialises the State object that holds the state of the app.        The default settings are read from Options.py and bayescustomize.ini        and are then overridden by the command-line processing code in the        __main__ code below."""        self.logFile = None        self.bayes = None        self.platform_mutex = None        self.prepared = False        self.can_stop = True        self.init()        # Load up the other settings from Option.py / bayescustomize.ini        self.uiPort = options["html_ui", "port"]        self.launchUI = options["html_ui", "launch_browser"]        self.gzipCache = options["Storage", "cache_use_gzip"]        self.cacheExpiryDays = options["Storage", "cache_expiry_days"]        self.runTestServer = False        self.isTest = False    def init(self):        assert not self.prepared, "init after prepare, but before close"        # Load the environment for translation.        self.lang_manager = i18n.LanguageManager()        # Set the system user default language.        self.lang_manager.set_language(\            self.lang_manager.locale_default_lang())        # Set interface to use the user language in the configuration file.        for language in reversed(options["globals", "language"]):            # We leave the default in there as the last option, to fall            # back on if necessary.            self.lang_manager.add_language(language)

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -