Compare commits
	
		
			2 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 7de6ed476a | |||
| 8b8b8c85fd | 
							
								
								
									
										90
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										90
									
								
								main.py
									
									
									
									
									
								
							@ -1,59 +1,31 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from twisted.cred.checkers import ICredentialsChecker
 | 
			
		||||
from twisted.cred import credentials, error
 | 
			
		||||
from twisted.python import failure
 | 
			
		||||
from twisted.internet import reactor, defer
 | 
			
		||||
from twisted.internet.task import LoopingCall
 | 
			
		||||
 | 
			
		||||
from zope.interface import implements
 | 
			
		||||
 | 
			
		||||
from mech_smtp import SerpentSMTPFactory, smtp_portal
 | 
			
		||||
from mech_imap import SerpentIMAPFactory, imap_portal
 | 
			
		||||
from serpent.usrpwd import dbs
 | 
			
		||||
from serpent.queue import squeue
 | 
			
		||||
from serpent.config import conf
 | 
			
		||||
 | 
			
		||||
class CredChecker(object):
 | 
			
		||||
    '''Класс проверки данных авторизации.
 | 
			
		||||
Параметром в конструктор передаётся список (list()) объектов баз пользователей.'''
 | 
			
		||||
    implements(ICredentialsChecker)
 | 
			
		||||
    credentialInterfaces = (credentials.IUsernamePassword,
 | 
			
		||||
                            credentials.IUsernameHashedPassword)
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, dbs):
 | 
			
		||||
        self.dbs = dbs
 | 
			
		||||
    
 | 
			
		||||
    def _cbPasswordMatch(self, matched, username):
 | 
			
		||||
        if matched:
 | 
			
		||||
            return username
 | 
			
		||||
        else:
 | 
			
		||||
            return failure.Failure(error.UnauthorizedLogin())
 | 
			
		||||
    
 | 
			
		||||
    def requestAvatarId(self, credentials):
 | 
			
		||||
        found_user = False
 | 
			
		||||
        for db in self.dbs:
 | 
			
		||||
            found_user = db.user_exist(credentials.username)
 | 
			
		||||
            if found_user:
 | 
			
		||||
                pwdfunc = db.check_pw
 | 
			
		||||
                break
 | 
			
		||||
        if found_user:
 | 
			
		||||
            return defer.maybeDeferred(
 | 
			
		||||
                pwdfunc, [credentials.username, credentials.password]).addCallback(
 | 
			
		||||
                self._cbPasswordMatch, str(credentials.username))
 | 
			
		||||
        else:
 | 
			
		||||
            return defer.fail(error.UnauthorizedLogin())
 | 
			
		||||
 | 
			
		||||
checker = CredChecker(dbs)
 | 
			
		||||
smtp_portal.registerChecker(checker)
 | 
			
		||||
smtp_factory = SerpentSMTPFactory(smtp_portal)
 | 
			
		||||
imap_portal.registerChecker(checker)
 | 
			
		||||
imap_factory = SerpentIMAPFactory(imap_portal)
 | 
			
		||||
    
 | 
			
		||||
reactor.listenTCP(2500, smtp_factory)
 | 
			
		||||
reactor.listenTCP(1430, imap_factory)
 | 
			
		||||
 | 
			
		||||
qtask = LoopingCall(squeue.run)
 | 
			
		||||
qtask.start(conf.smtp_queue_check_period)
 | 
			
		||||
 | 
			
		||||
reactor.run()
 | 
			
		||||
import asyncio
 | 
			
		||||
 
 | 
			
		||||
clients = {} # task -> (reader, writer)
 | 
			
		||||
 
 | 
			
		||||
def client_connected_handler(client_reader, client_writer):
 | 
			
		||||
    # Start a new asyncio.Task to handle this specific client connection
 | 
			
		||||
    task = asyncio.Task(handle_client(client_reader, client_writer))
 | 
			
		||||
    clients[task] = (client_reader, client_writer)
 | 
			
		||||
 
 | 
			
		||||
    def client_done(task):
 | 
			
		||||
        # When the tasks that handles the specific client connection is done
 | 
			
		||||
        del clients[task]
 | 
			
		||||
 
 | 
			
		||||
    # Add the client_done callback to be run when the future becomes done
 | 
			
		||||
    task.add_done_callback(client_done)
 | 
			
		||||
 
 | 
			
		||||
@asyncio.coroutine
 | 
			
		||||
def handle_client(client_reader, client_writer):
 | 
			
		||||
    # Handle the requests for a specific client with a line oriented protocol
 | 
			
		||||
    while True:
 | 
			
		||||
        # Read a line
 | 
			
		||||
        data = (yield from client_reader.readline())
 | 
			
		||||
        # Send it back to the client
 | 
			
		||||
        client_writer.write(data)
 | 
			
		||||
 
 | 
			
		||||
loop = asyncio.get_event_loop()
 | 
			
		||||
server = loop.run_until_complete(asyncio.start_server(client_connected_handler, 'localhost', 2222))
 | 
			
		||||
try:
 | 
			
		||||
    loop.run_forever()
 | 
			
		||||
finally:
 | 
			
		||||
    loop.close()
 | 
			
		||||
							
								
								
									
										305
									
								
								mech_imap.py
									
									
									
									
									
								
							
							
						
						
									
										305
									
								
								mech_imap.py
									
									
									
									
									
								
							@ -1,305 +0,0 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from zope.interface import implements
 | 
			
		||||
 | 
			
		||||
from twisted.cred import portal
 | 
			
		||||
from twisted.internet import protocol, ssl
 | 
			
		||||
from twisted.mail import imap4
 | 
			
		||||
 | 
			
		||||
from serpent.config import conf
 | 
			
		||||
from serpent.imap.mailbox import IMAPMailbox
 | 
			
		||||
from serpent.misc import IMAP_HDELIM, IMAP_MBOX_REG, IMAP_ACC_CONN_NUM
 | 
			
		||||
from shutil import rmtree, move
 | 
			
		||||
 | 
			
		||||
class IMAPUserAccount(object):
 | 
			
		||||
    implements(imap4.IAccount)
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, mdir):
 | 
			
		||||
        if not os.path.exists(mdir):
 | 
			
		||||
            os.makedirs(mdir)
 | 
			
		||||
        self.dir = mdir
 | 
			
		||||
        if self.dir in IMAP_MBOX_REG.keys():
 | 
			
		||||
            IMAP_MBOX_REG[self.dir][IMAP_ACC_CONN_NUM] += 1
 | 
			
		||||
        else:
 | 
			
		||||
            IMAP_MBOX_REG[self.dir] = {}
 | 
			
		||||
            IMAP_MBOX_REG[self.dir][IMAP_ACC_CONN_NUM] = 0
 | 
			
		||||
        for m in conf.imap_auto_mbox:
 | 
			
		||||
            if m not in IMAP_MBOX_REG[self.dir].keys():
 | 
			
		||||
                if isinstance(m, unicode):
 | 
			
		||||
                    m = m.encode('imap4-utf-7')
 | 
			
		||||
                IMAP_MBOX_REG[self.dir][m] = IMAPMailbox(os.path.join(self.dir, m))
 | 
			
		||||
                IMAP_MBOX_REG[self.dir][m]._start_monitor()
 | 
			
		||||
                self.subscribe(m)
 | 
			
		||||
 | 
			
		||||
    def _getMailbox(self, path):
 | 
			
		||||
        if isinstance(path, unicode):
 | 
			
		||||
            path = path.encode('imap4-utf-7')
 | 
			
		||||
        fullPath = os.path.join(self.dir, path)
 | 
			
		||||
        mbox = IMAPMailbox(fullPath)
 | 
			
		||||
        mbox._start_monitor()
 | 
			
		||||
        return mbox
 | 
			
		||||
 | 
			
		||||
    def listMailboxes(self, ref, wildcard):
 | 
			
		||||
        for box in os.listdir(self.dir):
 | 
			
		||||
            yield box.decode('imap4-utf-7'), self.create(box)
 | 
			
		||||
 | 
			
		||||
    def select(self, path, rw=False):
 | 
			
		||||
        if isinstance(path, unicode):
 | 
			
		||||
            path = path.encode('imap4-utf-7')
 | 
			
		||||
        if path in IMAP_MBOX_REG[self.dir].keys():
 | 
			
		||||
            return IMAP_MBOX_REG[self.dir][path]
 | 
			
		||||
        else:
 | 
			
		||||
            if path in os.listdir(self.dir):
 | 
			
		||||
                return self.create(path)
 | 
			
		||||
            else:
 | 
			
		||||
                raise imap4.NoSuchMailbox, path
 | 
			
		||||
    
 | 
			
		||||
    def addMailbox(self, name, mbox = None):
 | 
			
		||||
        if mbox:
 | 
			
		||||
            raise NotImplementedError
 | 
			
		||||
        return self.create(name)
 | 
			
		||||
 | 
			
		||||
    def create(self, pathspec):
 | 
			
		||||
        if isinstance(pathspec, unicode):
 | 
			
		||||
            pathspec = pathspec.encode('imap4-utf-7')
 | 
			
		||||
        if pathspec not in IMAP_MBOX_REG[self.dir].keys():
 | 
			
		||||
            paths = filter(None, pathspec.split(IMAP_HDELIM))
 | 
			
		||||
            for accum in range(1, len(paths)):
 | 
			
		||||
                subpath = IMAP_HDELIM.join(paths[:accum])
 | 
			
		||||
                if subpath not in IMAP_MBOX_REG[self.dir].keys():
 | 
			
		||||
                    try:
 | 
			
		||||
                        IMAP_MBOX_REG[self.dir][subpath] = self._getMailbox(IMAP_HDELIM.join(paths[:accum]))
 | 
			
		||||
                    except imap4.MailboxCollision:
 | 
			
		||||
                        pass
 | 
			
		||||
            IMAP_MBOX_REG[self.dir][pathspec] = self._getMailbox(pathspec)
 | 
			
		||||
        return IMAP_MBOX_REG[self.dir][pathspec]
 | 
			
		||||
    
 | 
			
		||||
    def delete(self, pathspec):
 | 
			
		||||
        if isinstance(pathspec, unicode):
 | 
			
		||||
            pathspec = pathspec.encode('imap4-utf-7')
 | 
			
		||||
        if pathspec in conf.imap_auto_mbox:
 | 
			
		||||
            raise imap4.MailboxException, pathspec
 | 
			
		||||
        if pathspec not in IMAP_MBOX_REG[self.dir].keys():
 | 
			
		||||
            raise imap4.MailboxException("No such mailbox")
 | 
			
		||||
        inferiors = self._inferiorNames(pathspec)
 | 
			
		||||
        if r'\Noselect' in IMAP_MBOX_REG[self.dir][pathspec].getFlags():
 | 
			
		||||
            # Check for hierarchically inferior mailboxes with this one
 | 
			
		||||
            # as part of their root.
 | 
			
		||||
            for inferior in inferiors:
 | 
			
		||||
                if inferior != pathspec:
 | 
			
		||||
                    raise imap4.MailboxException, "Hierarchically inferior mailboxes exist and \\Noselect is set"
 | 
			
		||||
        for inferior in inferiors:
 | 
			
		||||
            mdir = IMAP_MBOX_REG[self.dir][inferior].path
 | 
			
		||||
            IMAP_MBOX_REG[self.dir][inferior].destroy()
 | 
			
		||||
            del IMAP_MBOX_REG[self.dir][inferior]
 | 
			
		||||
            rmtree(mdir)
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def rename(self, oldname, newname):
 | 
			
		||||
        if oldname in conf.imap_auto_mbox:
 | 
			
		||||
            raise imap4.MailboxException, oldname
 | 
			
		||||
        if isinstance(oldname, unicode):
 | 
			
		||||
            oldname = oldname.encode('imap4-utf-7')
 | 
			
		||||
        if isinstance(newname, unicode):
 | 
			
		||||
            newname = newname.encode('imap4-utf-7')
 | 
			
		||||
        if oldname not in IMAP_MBOX_REG[self.dir].keys():
 | 
			
		||||
            raise imap4.NoSuchMailbox, oldname
 | 
			
		||||
        inferiors = [(o, o.replace(oldname, newname, 1)) for o in self._inferiorNames(oldname)]
 | 
			
		||||
        for (old, new) in inferiors:
 | 
			
		||||
            if new in IMAP_MBOX_REG[self.dir].keys():
 | 
			
		||||
                raise imap4.MailboxCollision, new
 | 
			
		||||
        for (old, new) in inferiors:
 | 
			
		||||
            move(os.path.join(self.dir, old), os.path.join(self.dir, new))
 | 
			
		||||
            IMAP_MBOX_REG[self.dir][new] = IMAP_MBOX_REG[self.dir][old]
 | 
			
		||||
            IMAP_MBOX_REG[self.dir][new].path = os.path.join(self.dir, new)
 | 
			
		||||
            IMAP_MBOX_REG[self.dir][new].path_msg_info = os.path.join(self.dir, conf.imap_msg_info)
 | 
			
		||||
            IMAP_MBOX_REG[self.dir][new].path_mbox_info = os.path.join(self.dir, conf.imap_mbox_info)
 | 
			
		||||
            del IMAP_MBOX_REG[self.dir][old]
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def subscribe(self, name):
 | 
			
		||||
        if isinstance(name, unicode):
 | 
			
		||||
            name = name.encode('imap4-utf-7')
 | 
			
		||||
        if name in IMAP_MBOX_REG[self.dir].keys():
 | 
			
		||||
            IMAP_MBOX_REG[self.dir][name].subscribe()
 | 
			
		||||
 | 
			
		||||
    def unsubscribe(self, name):
 | 
			
		||||
        if name in conf.imap_auto_mbox:
 | 
			
		||||
            raise imap4.MailboxException, name
 | 
			
		||||
        if isinstance(name, unicode):
 | 
			
		||||
            name = name.encode('imap4-utf-7')
 | 
			
		||||
        if name in IMAP_MBOX_REG[self.dir].keys():
 | 
			
		||||
            IMAP_MBOX_REG[self.dir][name].unsubscribe()
 | 
			
		||||
 | 
			
		||||
    def isSubscribed(self, name):
 | 
			
		||||
        if isinstance(name, unicode):
 | 
			
		||||
            name = name.encode('imap4-utf-7')
 | 
			
		||||
        return IMAP_MBOX_REG[self.dir][name].is_subscribed()
 | 
			
		||||
    
 | 
			
		||||
    def _inferiorNames(self, name):
 | 
			
		||||
        name_l = name.split(IMAP_HDELIM)
 | 
			
		||||
        inferiors = []
 | 
			
		||||
        for infname in IMAP_MBOX_REG[self.dir].keys():
 | 
			
		||||
            if name_l == infname.split(IMAP_HDELIM)[:len(name_l)]:
 | 
			
		||||
                inferiors.append(infname)
 | 
			
		||||
        return inferiors
 | 
			
		||||
 | 
			
		||||
class SerpentIMAPRealm(object):
 | 
			
		||||
    implements(portal.IRealm)
 | 
			
		||||
 | 
			
		||||
    def requestAvatar(self, avatarId, mind, *interfaces):
 | 
			
		||||
        if imap4.IAccount not in interfaces:
 | 
			
		||||
            raise NotImplementedError(
 | 
			
		||||
                "This realm only supports the imap4.IAccount interface.")
 | 
			
		||||
        mdir = os.path.join(conf.app_dir, conf.maildir_user_path % avatarId)
 | 
			
		||||
        avatar = IMAPUserAccount(mdir)
 | 
			
		||||
        return imap4.IAccount, avatar, lambda: None
 | 
			
		||||
 | 
			
		||||
###############################################################################
 | 
			
		||||
 | 
			
		||||
class IMAPServerProtocol(imap4.IMAP4Server):
 | 
			
		||||
    def lineReceived(self, line):
 | 
			
		||||
        if isinstance(line, unicode):
 | 
			
		||||
            line = line.encode('utf-8')
 | 
			
		||||
        print "CLIENT:", line
 | 
			
		||||
        imap4.IMAP4Server.lineReceived(self, line)
 | 
			
		||||
 | 
			
		||||
    def sendLine(self, line):
 | 
			
		||||
        imap4.IMAP4Server.sendLine(self, line)
 | 
			
		||||
        if isinstance(line, unicode):
 | 
			
		||||
            line = line.encode('utf-8')
 | 
			
		||||
        print "SERVER:", line
 | 
			
		||||
    
 | 
			
		||||
    def connectionLost(self, reason):
 | 
			
		||||
        self.setTimeout(None)
 | 
			
		||||
        if self.account and self.account.dir in IMAP_MBOX_REG.keys():
 | 
			
		||||
            IMAP_MBOX_REG[self.account.dir][IMAP_ACC_CONN_NUM] -= 1
 | 
			
		||||
            if IMAP_MBOX_REG[self.account.dir][IMAP_ACC_CONN_NUM] <= 0:
 | 
			
		||||
                for m in IMAP_MBOX_REG[self.account.dir].keys():
 | 
			
		||||
                    if m == IMAP_ACC_CONN_NUM:
 | 
			
		||||
                        continue
 | 
			
		||||
                    IMAP_MBOX_REG[self.account.dir][m].close()
 | 
			
		||||
                    del IMAP_MBOX_REG[self.account.dir][m]
 | 
			
		||||
                del IMAP_MBOX_REG[self.account.dir]
 | 
			
		||||
            self.account = None
 | 
			
		||||
    
 | 
			
		||||
    def _parseMbox(self, name):
 | 
			
		||||
        if isinstance(name, unicode):
 | 
			
		||||
            return name
 | 
			
		||||
        try:
 | 
			
		||||
            return name.decode('imap4-utf-7')
 | 
			
		||||
        except:
 | 
			
		||||
            #log.err()
 | 
			
		||||
            raise imap4.IllegalMailboxEncoding(name)
 | 
			
		||||
    
 | 
			
		||||
    def _cbCopySelectedMailbox(self, mbox, tag, messages, mailbox, uid):
 | 
			
		||||
        if not isinstance(mbox, IMAPMailbox):
 | 
			
		||||
            self.sendNegativeResponse(tag, 'No such mailbox: ' + mailbox)
 | 
			
		||||
        else:
 | 
			
		||||
            imap4.maybeDeferred(self.mbox.fetch, messages, uid
 | 
			
		||||
                ).addCallback(self.__cbCopy, tag, mbox
 | 
			
		||||
                ).addCallback(self.__cbCopied, tag, mbox
 | 
			
		||||
                ).addErrback(self.__ebCopy, tag
 | 
			
		||||
                )
 | 
			
		||||
    
 | 
			
		||||
    def __cbCopy(self, messages, tag, mbox):
 | 
			
		||||
        # XXX - This should handle failures with a rollback or something
 | 
			
		||||
        addedDeferreds = []
 | 
			
		||||
        fastCopyMbox = imap4.IMessageCopier(mbox, None)
 | 
			
		||||
        for (_id, msg) in messages:
 | 
			
		||||
            if fastCopyMbox is not None:
 | 
			
		||||
                d = imap4.maybeDeferred(fastCopyMbox.copy, msg)
 | 
			
		||||
                addedDeferreds.append(d)
 | 
			
		||||
                continue
 | 
			
		||||
            # XXX - The following should be an implementation of IMessageCopier.copy
 | 
			
		||||
            # on an IMailbox->IMessageCopier adapter.
 | 
			
		||||
            flags = msg.getFlags()
 | 
			
		||||
            date = msg.getInternalDate()
 | 
			
		||||
            body = imap4.IMessageFile(msg, None)
 | 
			
		||||
            if body is not None:
 | 
			
		||||
                bodyFile = body.open()
 | 
			
		||||
                d = imap4.maybeDeferred(mbox.addMessage, bodyFile, flags, date)
 | 
			
		||||
            else:
 | 
			
		||||
                def rewind(f):
 | 
			
		||||
                    f.seek(0)
 | 
			
		||||
                    return f
 | 
			
		||||
                _buffer = imap4.tempfile.TemporaryFile()
 | 
			
		||||
                d = imap4.MessageProducer(msg, _buffer, self._scheduler
 | 
			
		||||
                    ).beginProducing(None
 | 
			
		||||
                    ).addCallback(lambda _, b=_buffer, f=flags, d=date: mbox.addMessage(rewind(b), f, d)
 | 
			
		||||
                    )
 | 
			
		||||
            addedDeferreds.append(d)
 | 
			
		||||
        return imap4.defer.DeferredList(addedDeferreds)
 | 
			
		||||
    def __cbCopied(self, deferredIds, tag, mbox):
 | 
			
		||||
        ids = []
 | 
			
		||||
        failures = []
 | 
			
		||||
        for (status, result) in deferredIds:
 | 
			
		||||
            if status:
 | 
			
		||||
                ids.append(result)
 | 
			
		||||
            else:
 | 
			
		||||
                failures.append(result.value)
 | 
			
		||||
        if failures:
 | 
			
		||||
            self.sendNegativeResponse(tag, '[ALERT] Some messages were not copied')
 | 
			
		||||
        else:
 | 
			
		||||
            self.sendPositiveResponse(tag, 'COPY completed')
 | 
			
		||||
    def __ebCopy(self, failure, tag):
 | 
			
		||||
        self.sendBadResponse(tag, 'COPY failed:' + str(failure.value))
 | 
			
		||||
        #log.err(failure)
 | 
			
		||||
        
 | 
			
		||||
    def _cbAppendGotMailbox(self, mbox, tag, flags, date, message):
 | 
			
		||||
        if not isinstance(mbox, IMAPMailbox):
 | 
			
		||||
            self.sendNegativeResponse(tag, '[TRYCREATE] No such mailbox')
 | 
			
		||||
            return
 | 
			
		||||
        d = mbox.addMessage(message, flags, date)
 | 
			
		||||
        d.addCallback(self.__cbAppend, tag, mbox)
 | 
			
		||||
        d.addErrback(self.__ebAppend, tag)
 | 
			
		||||
        
 | 
			
		||||
    def __cbAppend(self, result, tag, mbox):
 | 
			
		||||
        self.sendUntaggedResponse('%d EXISTS' % mbox.getMessageCount())
 | 
			
		||||
        self.sendPositiveResponse(tag, 'APPEND complete')
 | 
			
		||||
    def __ebAppend(self, failure, tag):
 | 
			
		||||
        self.sendBadResponse(tag, 'APPEND failed: ' + str(failure.value))
 | 
			
		||||
 | 
			
		||||
    def _cbStatusGotMailbox(self, mbox, tag, mailbox, names):
 | 
			
		||||
        if isinstance(mbox, IMAPMailbox):
 | 
			
		||||
            
 | 
			
		||||
            imap4.maybeDeferred(mbox.requestStatus, names).addCallbacks(
 | 
			
		||||
                self.__cbStatus, self.__ebStatus,
 | 
			
		||||
                (tag, mailbox), None, (tag, mailbox), None
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            self.sendNegativeResponse(tag, "Could not open mailbox")
 | 
			
		||||
    def _ebStatusGotMailbox(self, failure, tag):
 | 
			
		||||
        self.sendBadResponse(tag, "Server error encountered while opening mailbox.")
 | 
			
		||||
        #log.err(failure)
 | 
			
		||||
 | 
			
		||||
    def __cbStatus(self, status, tag, box):
 | 
			
		||||
        line = ' '.join(['%s %s' % x for x in status.iteritems()])
 | 
			
		||||
        if isinstance(box, unicode):
 | 
			
		||||
            box = box.encode('imap4-utf-7')
 | 
			
		||||
        self.sendUntaggedResponse('STATUS %s (%s)' % (box, line))
 | 
			
		||||
        self.sendPositiveResponse(tag, 'STATUS complete')
 | 
			
		||||
    def __ebStatus(self, failure, tag, box):
 | 
			
		||||
        self.sendBadResponse(tag, 'STATUS %s failed: %s' % (box, str(failure.value)))
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
 | 
			
		||||
class SerpentIMAPFactory(protocol.Factory):
 | 
			
		||||
    def __init__(self, portal):
 | 
			
		||||
        self.portal = portal
 | 
			
		||||
 | 
			
		||||
    def buildProtocol(self, addr):
 | 
			
		||||
        contextFactory = None
 | 
			
		||||
        if conf.tls:
 | 
			
		||||
            tls_data = file(conf.tls_pem, 'rb').read()
 | 
			
		||||
            cert = ssl.PrivateCertificate.loadPEM(tls_data)
 | 
			
		||||
            contextFactory = cert.options()
 | 
			
		||||
        p = IMAPServerProtocol(contextFactory = contextFactory)
 | 
			
		||||
        if conf.tls:
 | 
			
		||||
            p.canStartTLS = True
 | 
			
		||||
        p.IDENT = '%s ready' % conf.SRVNAME
 | 
			
		||||
        p.portal = self.portal
 | 
			
		||||
        return p
 | 
			
		||||
imap_portal = portal.Portal(SerpentIMAPRealm())
 | 
			
		||||
							
								
								
									
										140
									
								
								mech_smtp.py
									
									
									
									
									
								
							
							
						
						
									
										140
									
								
								mech_smtp.py
									
									
									
									
									
								
							@ -1,140 +0,0 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from serpent.config import conf
 | 
			
		||||
from serpent import rules
 | 
			
		||||
from serpent.queue import squeue
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from email.Header import Header
 | 
			
		||||
from zope.interface import implements
 | 
			
		||||
 | 
			
		||||
from twisted.internet import defer, ssl
 | 
			
		||||
from twisted.mail import smtp
 | 
			
		||||
from twisted.mail.imap4 import LOGINCredentials, PLAINCredentials
 | 
			
		||||
 | 
			
		||||
from twisted.cred.portal import IRealm, Portal
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SmtpMessageDelivery:
 | 
			
		||||
    implements(smtp.IMessageDelivery)
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, avatarId = None):
 | 
			
		||||
        self.avatarId = avatarId
 | 
			
		||||
 | 
			
		||||
    def receivedHeader(self, helo, origin, recipients):
 | 
			
		||||
        header = conf.smtp_header.format(
 | 
			
		||||
                                     sender_ip = helo[1],
 | 
			
		||||
                                     sender_host = helo[0],
 | 
			
		||||
                                     srv_host = conf.smtp_hostname,
 | 
			
		||||
                                     srv_info = conf.srv_version,
 | 
			
		||||
                                     sender = conf.smtp_email_delim.join([origin.local, origin.domain]),
 | 
			
		||||
                                     id = self.messageid,
 | 
			
		||||
                                     rcpt = conf.smtp_email_delim.join([recipients[0].dest.local, recipients[0].dest.domain]),
 | 
			
		||||
                                     date = smtp.rfc822date()
 | 
			
		||||
                                     )
 | 
			
		||||
        return 'Received: %s' % Header(header)
 | 
			
		||||
    
 | 
			
		||||
    def validateFrom(self, helo, origin):       # Надо воткнуть всякие проверки хоста по HELO
 | 
			
		||||
        try:
 | 
			
		||||
            rules.validateFrom(self, [origin.local, origin.domain])
 | 
			
		||||
        except:
 | 
			
		||||
            raise
 | 
			
		||||
        else:
 | 
			
		||||
            return origin
 | 
			
		||||
    
 | 
			
		||||
    def validateTo(self, user):
 | 
			
		||||
        self.messageid = smtp.messageid().split('@')[0].strip('<')
 | 
			
		||||
        try:
 | 
			
		||||
            rules.validateTo(self, user)
 | 
			
		||||
        except:
 | 
			
		||||
            raise
 | 
			
		||||
        else:
 | 
			
		||||
            msg = {
 | 
			
		||||
                       'from': [user.orig.local, user.orig.domain],
 | 
			
		||||
                       'rcpt': [user.dest.local, user.dest.domain],
 | 
			
		||||
                       'transaction_id': self.messageid,
 | 
			
		||||
                       'id': smtp.messageid().split('@')[0].strip('<')
 | 
			
		||||
                       }
 | 
			
		||||
            return lambda: SmtpMessage(msg)
 | 
			
		||||
 | 
			
		||||
class SmtpMessage:
 | 
			
		||||
    implements(smtp.IMessage)
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, msg):
 | 
			
		||||
        self.lines = []
 | 
			
		||||
        self.size = 0
 | 
			
		||||
        self.msg = msg
 | 
			
		||||
 | 
			
		||||
    def lineReceived(self, line):
 | 
			
		||||
        self.lines.append(line)
 | 
			
		||||
    
 | 
			
		||||
    def eomReceived(self):
 | 
			
		||||
        self.lines.append('')
 | 
			
		||||
        self.msg['message'] = "\n".join(self.lines)
 | 
			
		||||
        self.lines = None
 | 
			
		||||
        return defer.succeed(squeue.add(self.msg))
 | 
			
		||||
    
 | 
			
		||||
    def connectionLost(self):
 | 
			
		||||
        # There was an error, throw away the stored lines
 | 
			
		||||
        self.lines = None
 | 
			
		||||
 | 
			
		||||
class SerpentESMTP(smtp.ESMTP):
 | 
			
		||||
    def ext_AUTH(self, rest):
 | 
			
		||||
        if self.canStartTLS and not self.startedTLS:
 | 
			
		||||
            self.sendCode(538, 'Unencrypted auth denied')
 | 
			
		||||
            return
 | 
			
		||||
        if self.authenticated:
 | 
			
		||||
            self.sendCode(503, 'Already authenticated')
 | 
			
		||||
            return
 | 
			
		||||
        parts = rest.split(None, 1)
 | 
			
		||||
        chal = self.challengers.get(parts[0].upper(), lambda: None)()
 | 
			
		||||
        if not chal:
 | 
			
		||||
            self.sendCode(504, 'Unrecognized authentication type')
 | 
			
		||||
            return
 | 
			
		||||
        self.mode = smtp.AUTH
 | 
			
		||||
        self.challenger = chal
 | 
			
		||||
        if len(parts) > 1:
 | 
			
		||||
            chal.getChallenge() # Discard it, apparently the client does not
 | 
			
		||||
                                # care about it.
 | 
			
		||||
            rest = parts[1]
 | 
			
		||||
        else:
 | 
			
		||||
            rest = None
 | 
			
		||||
        self.state_AUTH(rest)
 | 
			
		||||
 | 
			
		||||
class SerpentSMTPFactory(smtp.SMTPFactory):
 | 
			
		||||
    protocol = SerpentESMTP
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *a, **kw):
 | 
			
		||||
        smtp.SMTPFactory.__init__(self, *a, **kw)
 | 
			
		||||
        self.delivery = SmtpMessageDelivery()
 | 
			
		||||
 | 
			
		||||
    def buildProtocol(self, addr):
 | 
			
		||||
        contextFactory = None
 | 
			
		||||
        if conf.tls:
 | 
			
		||||
            tls_data = file(conf.tls_pem, 'rb').read()
 | 
			
		||||
            cert = ssl.PrivateCertificate.loadPEM(tls_data)
 | 
			
		||||
            contextFactory = cert.options()
 | 
			
		||||
        p = smtp.SMTPFactory.buildProtocol(self, addr)
 | 
			
		||||
        p.ctx = contextFactory
 | 
			
		||||
        if conf.tls:
 | 
			
		||||
            p.canStartTLS = True
 | 
			
		||||
        p.host = conf.smtp_hostname
 | 
			
		||||
        p.delivery = self.delivery
 | 
			
		||||
        p.challengers = {"LOGIN": LOGINCredentials, "PLAIN": PLAINCredentials}
 | 
			
		||||
        return p
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SmtpRealm:
 | 
			
		||||
    implements(IRealm)
 | 
			
		||||
 | 
			
		||||
    def requestAvatar(self, avatarId, mind, *interfaces):
 | 
			
		||||
        if smtp.IMessageDelivery in interfaces:
 | 
			
		||||
            return smtp.IMessageDelivery, SmtpMessageDelivery(avatarId), lambda: None
 | 
			
		||||
        raise NotImplementedError()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
smtp_portal = Portal(SmtpRealm())
 | 
			
		||||
@ -1,36 +0,0 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
class Config(object):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
conf = Config()
 | 
			
		||||
conf.VERSION = '0.1.0'
 | 
			
		||||
conf.SRVNAME = 'Serpent'
 | 
			
		||||
conf.srv_version = '%s %s' % (conf.SRVNAME, conf.VERSION)
 | 
			
		||||
conf.local_domains = ['dom.lan']    # Список доменов, для которых будет приниматься почта
 | 
			
		||||
conf.tls = True
 | 
			
		||||
conf.tls_pem = './serpent.pem'
 | 
			
		||||
conf.smtp_open_relay = False        # Разрешить ли пересылку откуда угодно куда угодно
 | 
			
		||||
conf.smtp_email_delim = '@'
 | 
			
		||||
conf.smtp_header = '''from [{sender_ip}] (helo={sender_host})
 | 
			
		||||
    by {srv_host} with ESMTP ({srv_info})
 | 
			
		||||
    (envelope-from <{sender}>)
 | 
			
		||||
    id {id}
 | 
			
		||||
    for {rcpt}; {date}
 | 
			
		||||
'''
 | 
			
		||||
conf.smtp_hostname = 'mail.dom.lan'
 | 
			
		||||
conf.app_dir = '/home/inpos/tmp/serpent'
 | 
			
		||||
conf.smtp_queue_dir = 'smtp_queue'
 | 
			
		||||
conf.smtp_message_size = 40                 # Размер в МБ
 | 
			
		||||
conf.smtp_queue_check_period = 30           # Период запуска обработки очереди в минутах
 | 
			
		||||
conf.smtp_queue_message_ttl = 3 * 24 * 60   # Время жизни сообщения в очереди в минутах
 | 
			
		||||
conf.maildir_user_path = 'mailstore/%s/'
 | 
			
		||||
conf.smtp_email_tls_required = True
 | 
			
		||||
 | 
			
		||||
conf.imap_SENT = 'Sent'
 | 
			
		||||
conf.imap_TRASH = 'Trash'
 | 
			
		||||
conf.imap_subscribed = '.subscribed'
 | 
			
		||||
conf.imap_msg_info = 'msg_info.db'
 | 
			
		||||
conf.imap_mbox_info = 'mbox_info.db'
 | 
			
		||||
conf.imap_auto_mbox = ['INBOX', 'Sent', 'Trash']
 | 
			
		||||
conf.imap_expunge_on_close = True
 | 
			
		||||
conf.imap_check_new_interval = 10.0         # Период проверки новых сообщений в ящике
 | 
			
		||||
@ -1,76 +0,0 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import pickle
 | 
			
		||||
from glob import iglob
 | 
			
		||||
from serpent.config import conf
 | 
			
		||||
from serpent.misc import IMAP_FLAGS
 | 
			
		||||
 | 
			
		||||
class SmtpFileStore(object):
 | 
			
		||||
    def __init__(self, dpath):
 | 
			
		||||
        self.path = dpath
 | 
			
		||||
        if not os.path.exists(dpath):
 | 
			
		||||
            os.makedirs(dpath)
 | 
			
		||||
    def read(self, fid):
 | 
			
		||||
        try:
 | 
			
		||||
            with open(os.path.join(self.path, fid), 'rb') as f, open(os.path.join(self.path, fid + '.i'), 'rb') as i:
 | 
			
		||||
                data = pickle.load(i)
 | 
			
		||||
                try:
 | 
			
		||||
                    data['message'] = f.read()
 | 
			
		||||
                except:
 | 
			
		||||
                    raise
 | 
			
		||||
            return data
 | 
			
		||||
        except:
 | 
			
		||||
            #return False
 | 
			
		||||
            raise
 | 
			
		||||
    def write(self, data):
 | 
			
		||||
        fid = data['id']
 | 
			
		||||
        try:
 | 
			
		||||
            with open(os.path.join(self.path, fid), 'wb') as f, open(os.path.join(self.path, fid + '.i'), 'wb') as i:
 | 
			
		||||
                m = data['message']
 | 
			
		||||
                data['message'] = ''
 | 
			
		||||
                try:
 | 
			
		||||
                    pickle.dump(data, i, 2)
 | 
			
		||||
                    f.write(m)
 | 
			
		||||
                except:
 | 
			
		||||
                    raise
 | 
			
		||||
            return True
 | 
			
		||||
        except:
 | 
			
		||||
            #return False
 | 
			
		||||
            raise
 | 
			
		||||
    def getinfo(self, fid):
 | 
			
		||||
        try:
 | 
			
		||||
            with open(os.path.join(self.path, fid + '.i'), 'rb') as i:
 | 
			
		||||
                data = pickle.load(i)
 | 
			
		||||
            return data
 | 
			
		||||
        except:
 | 
			
		||||
            return False
 | 
			
		||||
    def setinfo(self, data):
 | 
			
		||||
        try:
 | 
			
		||||
            with open(os.path.join(self.path, data['id'] + '.i'), 'wb') as i:
 | 
			
		||||
                pickle.dump(data, i, 2)
 | 
			
		||||
            return True
 | 
			
		||||
        except:
 | 
			
		||||
            return False
 | 
			
		||||
    def list(self):
 | 
			
		||||
        return [i.split('/')[-1].rstrip('\.i') for i in iglob(self.path + '*.i')]
 | 
			
		||||
    def delete(self, fid):
 | 
			
		||||
        os.remove(os.path.join(self.path, fid + '.i'))
 | 
			
		||||
        os.remove(os.path.join(self.path, fid))
 | 
			
		||||
 | 
			
		||||
class MailDirStore(object):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        from serpent.imap import mailbox
 | 
			
		||||
        self.mbox = mailbox
 | 
			
		||||
    def deliver(self, user, message):
 | 
			
		||||
        mdir = os.path.join(conf.app_dir, conf.maildir_user_path % user)
 | 
			
		||||
        if not os.path.exists(mdir):
 | 
			
		||||
            os.makedirs(mdir)
 | 
			
		||||
        inbox = os.path.join(mdir, 'INBOX')
 | 
			
		||||
        mailbox = self.mbox.IMAPMailbox(inbox)
 | 
			
		||||
        try:
 | 
			
		||||
            mailbox.addMessage(message, [IMAP_FLAGS['RECENT']])
 | 
			
		||||
            return True
 | 
			
		||||
        except:
 | 
			
		||||
            raise
 | 
			
		||||
        
 | 
			
		||||
@ -1,15 +0,0 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
from twisted.mail import smtp
 | 
			
		||||
### Исключения
 | 
			
		||||
class SMTPAuthReqError(smtp.SMTPServerError):
 | 
			
		||||
    '''Класс исключения. Сообщает о необходимости авторизации.'''
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        smtp.SMTPServerError.__init__(self, 550, 'Authentication required!')
 | 
			
		||||
 | 
			
		||||
class SMTPNotOpenRelay(smtp.SMTPServerError):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        smtp.SMTPServerError.__init__(self, 550, 'Not Open Relay!')
 | 
			
		||||
###
 | 
			
		||||
 | 
			
		||||
SMTPBadRcpt = smtp.SMTPBadRcpt
 | 
			
		||||
SMTPBadSender = smtp.SMTPBadSender
 | 
			
		||||
@ -1,313 +0,0 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from twisted.mail import maildir, imap4
 | 
			
		||||
from twisted.mail.smtp import rfc822date
 | 
			
		||||
from twisted.internet import inotify
 | 
			
		||||
from twisted.python import filepath
 | 
			
		||||
 | 
			
		||||
from zope.interface import implements
 | 
			
		||||
from threading import Thread
 | 
			
		||||
 | 
			
		||||
import random
 | 
			
		||||
import email
 | 
			
		||||
from pickle import load, dump
 | 
			
		||||
from StringIO import StringIO
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from serpent.config import conf
 | 
			
		||||
from serpent import misc
 | 
			
		||||
 | 
			
		||||
from sqlitedict import SqliteDict
 | 
			
		||||
 | 
			
		||||
class LoopingTask(Thread):
 | 
			
		||||
    def __init__(self, func, event, interval):
 | 
			
		||||
        Thread.__init__(self)
 | 
			
		||||
        self.func = func
 | 
			
		||||
        self.interval = interval
 | 
			
		||||
        self.stopped = event
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        while not self.stopped.wait(self.interval):
 | 
			
		||||
            self.func()
 | 
			
		||||
 | 
			
		||||
class SerpentAppendMessageTask(maildir._MaildirMailboxAppendMessageTask):
 | 
			
		||||
    
 | 
			
		||||
    def moveFileToNew(self):
 | 
			
		||||
        while True:
 | 
			
		||||
            newname = os.path.join(self.mbox.path, "new", maildir._generateMaildirName())
 | 
			
		||||
            try:
 | 
			
		||||
                self.osrename(self.tmpname, newname)
 | 
			
		||||
                break
 | 
			
		||||
            except OSError, (err, estr):
 | 
			
		||||
                import errno
 | 
			
		||||
                # if the newname exists, retry with a new newname.
 | 
			
		||||
                if err != errno.EEXIST:
 | 
			
		||||
                    self.fail()
 | 
			
		||||
                    newname = None
 | 
			
		||||
                    break
 | 
			
		||||
        if newname is not None:
 | 
			
		||||
            self.mbox.lastadded = newname
 | 
			
		||||
            self.defer.callback(None)
 | 
			
		||||
            self.defer = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ExtendedMaildir(maildir.MaildirMailbox):
 | 
			
		||||
    def __iter__(self):
 | 
			
		||||
        return iter(self.list)
 | 
			
		||||
 | 
			
		||||
    def __getitem__(self, i):
 | 
			
		||||
        return self.list[i]
 | 
			
		||||
 | 
			
		||||
class IMAPMailbox(ExtendedMaildir):
 | 
			
		||||
    implements(imap4.IMailbox, imap4.ICloseableMailbox)
 | 
			
		||||
    
 | 
			
		||||
    AppendFactory = SerpentAppendMessageTask
 | 
			
		||||
 | 
			
		||||
    def __init__(self, path):
 | 
			
		||||
        maildir.initializeMaildir(path)
 | 
			
		||||
        self.listeners = []
 | 
			
		||||
        self.path = path
 | 
			
		||||
        self.msg_info = SqliteDict(os.path.join(path, conf.imap_msg_info))
 | 
			
		||||
        self.mbox_info = SqliteDict(os.path.join(path, conf.imap_mbox_info))
 | 
			
		||||
        self.lastadded = None
 | 
			
		||||
        self.__check_flags_()
 | 
			
		||||
 | 
			
		||||
    def _start_monitor(self):
 | 
			
		||||
        self.notifier = inotify.INotify()
 | 
			
		||||
        self.notifier.startReading()
 | 
			
		||||
        self.notifier.watch(filepath.FilePath(os.path.join(self.path, 'new')),
 | 
			
		||||
                   callbacks=[self._new_files])
 | 
			
		||||
        self.notifier.watch(filepath.FilePath(os.path.join(self.path,'cur')),
 | 
			
		||||
                   callbacks=[self._new_files])
 | 
			
		||||
 | 
			
		||||
    def _new_files(self, wo, path, code):
 | 
			
		||||
        if code == inotify.IN_MOVED_TO or code == inotify.IN_DELETE:
 | 
			
		||||
            for l in self.listeners:
 | 
			
		||||
                l.newMessages(self.getMessageCount(), self.getRecentCount())
 | 
			
		||||
 | 
			
		||||
    def __check_flags_(self):
 | 
			
		||||
        if 'subscribed' not in self.mbox_info.keys(): self.mbox_info['subscribed'] = False
 | 
			
		||||
        if 'uidvalidity' not in self.mbox_info.keys(): self.mbox_info['uidvalidity'] = random.randint(0, 2**32)
 | 
			
		||||
        if 'uidnext' not in self.mbox_info.keys(): self.mbox_info['uidnext'] = 1
 | 
			
		||||
        self.mbox_info.commit(blocking=False)
 | 
			
		||||
        l = [l for l in self.__msg_list_()]
 | 
			
		||||
        for i in l:
 | 
			
		||||
            fn = i.split('/')[-1]
 | 
			
		||||
            if fn not in msg_info.keys():
 | 
			
		||||
                val1 = {'uid': self.getUIDNext()}
 | 
			
		||||
                if i.split('/')[-2] == 'new':
 | 
			
		||||
                    val1['flags'] = []
 | 
			
		||||
                else:
 | 
			
		||||
                    val1['flags'] = [misc.IMAP_FLAGS['SEEN']]
 | 
			
		||||
                self.msg_info[fn] = val1
 | 
			
		||||
        self.msg_info.commit(blocking=False)
 | 
			
		||||
 | 
			
		||||
    def subscribe(self):
 | 
			
		||||
        self.mbox_info['subscribed'] = True
 | 
			
		||||
        self.mbox_info.commit(blocking=False)
 | 
			
		||||
 | 
			
		||||
    def unsubscribe(self):
 | 
			
		||||
        self.mbox_info['subscribed'] = False
 | 
			
		||||
        self.mbox_info.commit(blocking=False)
 | 
			
		||||
    
 | 
			
		||||
    def is_subscribed(self):
 | 
			
		||||
        return self.mbox_info['subscribed']
 | 
			
		||||
 | 
			
		||||
    def __count_flagged_msgs_(self, flag):
 | 
			
		||||
        val1 = [0 for fn in self.msg_info.keys() if flag in self.msg_info[fn]['flags']]
 | 
			
		||||
        return len(val1)
 | 
			
		||||
    
 | 
			
		||||
    def getHierarchicalDelimiter(self):
 | 
			
		||||
        return misc.IMAP_HDELIM
 | 
			
		||||
 | 
			
		||||
    def getFlags(self):
 | 
			
		||||
        return misc.IMAP_FLAGS.values()
 | 
			
		||||
 | 
			
		||||
    def getMessageCount(self):
 | 
			
		||||
        val1 = [0 for fn in self.msg_info.keys() if misc.IMAP_FLAGS['DELETED'] not in self.msg_info[fn]['flags']]
 | 
			
		||||
        return len(val1)
 | 
			
		||||
 | 
			
		||||
    def getRecentCount(self):
 | 
			
		||||
        return self.__count_flagged_msgs_(misc.IMAP_FLAGS['RECENT'])
 | 
			
		||||
    
 | 
			
		||||
    def getUnseenCount(self):
 | 
			
		||||
        return self.getMessageCount() - self.__count_flagged_msgs_(misc.IMAP_FLAGS['SEEN'])
 | 
			
		||||
 | 
			
		||||
    def isWriteable(self):
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def getUIDValidity(self):
 | 
			
		||||
        return self.mbox_info['uidvalidity']
 | 
			
		||||
    
 | 
			
		||||
    def getUIDNext(self):
 | 
			
		||||
        un = self.mbox_info['uidnext']
 | 
			
		||||
        self.mbox_info['uidnext'] += 1
 | 
			
		||||
        self.mbox_info.commit(blocking=False)
 | 
			
		||||
        return un
 | 
			
		||||
    
 | 
			
		||||
    def getUID(self, num):
 | 
			
		||||
        return num
 | 
			
		||||
 | 
			
		||||
    def addMessage(self, message, flags = (), date = None):
 | 
			
		||||
        return self.appendMessage(message).addCallback(self._cbAddMessage, flags)
 | 
			
		||||
    
 | 
			
		||||
    def _cbAddMessage(self, obj, flags):
 | 
			
		||||
        path = self.lastadded
 | 
			
		||||
        self.lastadded = None
 | 
			
		||||
        fn = path.split('/')[-1]
 | 
			
		||||
        self.msg_info[fn] = {'uid': self.getUIDNext(), 'flags': flags}
 | 
			
		||||
        self.msg_info.commit(blocking=False)
 | 
			
		||||
        if misc.IMAP_FLAGS['SEEN'] in flags and path.split('/')[-2] != 'cur':
 | 
			
		||||
            new_path = os.path.join(self.path, 'cur', fn)
 | 
			
		||||
            os.rename(path, new_path)
 | 
			
		||||
 | 
			
		||||
    def __msg_list_(self):
 | 
			
		||||
        a = []
 | 
			
		||||
        for m in os.listdir(os.path.join(self.path, 'new')):
 | 
			
		||||
            a.append(os.path.join(self.path, 'new', m))
 | 
			
		||||
        for m in os.listdir(os.path.join(self.path, 'cur')):
 | 
			
		||||
            a.append(os.path.join(self.path, 'cur', m))
 | 
			
		||||
        return a
 | 
			
		||||
 | 
			
		||||
    def _seqMessageSetToSeqDict(self, messageSet):
 | 
			
		||||
        if not messageSet.last:
 | 
			
		||||
            messageSet.last = self.getMessageCount()
 | 
			
		||||
 | 
			
		||||
        seqMap = {}
 | 
			
		||||
        msgs = self.__msg_list_()
 | 
			
		||||
        for messageNum in messageSet:
 | 
			
		||||
            if messageNum > 0 and messageNum <= self.getMessageCount():
 | 
			
		||||
                seqMap[messageNum] = msgs[messageNum - 1]
 | 
			
		||||
        return seqMap
 | 
			
		||||
 | 
			
		||||
    def fetch(self, messages, uid):
 | 
			
		||||
        return [[seq, MaildirMessage(seq,
 | 
			
		||||
                                     file(filename, 'rb').read(),
 | 
			
		||||
                                     self.msg_info[filename.split('/')[-1]]['flags'],
 | 
			
		||||
                                     rfc822date())]
 | 
			
		||||
                for seq, filename in self.__fetch_(messages, uid).iteritems()]
 | 
			
		||||
    def __fetch_(self, messages, uid):
 | 
			
		||||
        if uid:
 | 
			
		||||
            messagesToFetch = {}
 | 
			
		||||
            if not messages.last:
 | 
			
		||||
                messages.last = self.mbox_info['uidnext']
 | 
			
		||||
            fn_uid = dict((fn, self.msg_info[fn]['uid']) for fn in self.msg_info.keys())
 | 
			
		||||
            for uid in messages:
 | 
			
		||||
                if uid in fn_uid.values():
 | 
			
		||||
                    for name, _id in fn_uid.iteritems():
 | 
			
		||||
                        if uid == _id:
 | 
			
		||||
                            if os.path.exists(os.path.join(self.path,'new', name)):
 | 
			
		||||
                                messagesToFetch[uid] = os.path.join(self.path,'new', name)
 | 
			
		||||
                            elif os.path.exists(os.path.join(self.path,'cur', name)):
 | 
			
		||||
                                messagesToFetch[uid] = os.path.join(self.path,'cur', name)
 | 
			
		||||
        else:
 | 
			
		||||
            messagesToFetch = self._seqMessageSetToSeqDict(messages)
 | 
			
		||||
        return messagesToFetch
 | 
			
		||||
    def store(self, messages, flags, mode, uid):
 | 
			
		||||
        d = {}
 | 
			
		||||
        for _id, path in self.__fetch_(messages, uid).iteritems():
 | 
			
		||||
            filename = path.split('/')[-1]
 | 
			
		||||
            if mode < 0:
 | 
			
		||||
                old_f = self.msg_info[filename]['flags']
 | 
			
		||||
                self.msg_info[filename]['flags'] = list(set(old_f).difference(set(flags)))
 | 
			
		||||
                if misc.IMAP_FLAGS['SEEN'] in flags and path.split('/')[-2] != 'new':
 | 
			
		||||
                    new_path = os.path.join(self.path, 'new', filename)
 | 
			
		||||
                    os.rename(path, new_path)
 | 
			
		||||
            elif mode == 0:
 | 
			
		||||
                self.msg_info[filename]['flags'] = flags
 | 
			
		||||
            elif mode > 0:
 | 
			
		||||
                old_f = self.msg_info[filename]['flags']
 | 
			
		||||
                self.msg_info[filename]['flags'] = list(set(old_f).union(set(flags)))
 | 
			
		||||
                if misc.IMAP_FLAGS['SEEN'] in flags and path.split('/')[-2] != 'cur':
 | 
			
		||||
                    new_path = os.path.join(self.path, 'cur', filename)
 | 
			
		||||
                    os.rename(path, new_path)
 | 
			
		||||
            d[_id] = self.msg_info[filename]['flags']
 | 
			
		||||
        msg_info.commit(blocking=False)
 | 
			
		||||
        return d
 | 
			
		||||
    
 | 
			
		||||
    def expunge(self):
 | 
			
		||||
        uids = []
 | 
			
		||||
        for path in self.__msg_list_():
 | 
			
		||||
            fn = path.split('/')[-1]
 | 
			
		||||
            if fn not in self.msg_info.keys():
 | 
			
		||||
                continue
 | 
			
		||||
            uid = self.msg_info[fn]['uid']
 | 
			
		||||
            if misc.IMAP_FLAGS['DELETED'] in self.msg_info[fn]['flags']:
 | 
			
		||||
                os.remove(path)
 | 
			
		||||
                del self.msg_info[fn]
 | 
			
		||||
                uids.append(uid)
 | 
			
		||||
        self.msg_info.commit(blocking=False)
 | 
			
		||||
        return uids
 | 
			
		||||
    
 | 
			
		||||
    def addListener(self, listener):
 | 
			
		||||
        self.listeners.append(listener)
 | 
			
		||||
 | 
			
		||||
    def removeListener(self, listener):
 | 
			
		||||
        self.listeners.remove(listener)
 | 
			
		||||
    
 | 
			
		||||
    def requestStatus(self, names):
 | 
			
		||||
        return imap4.statusRequestHelper(self, names)
 | 
			
		||||
    
 | 
			
		||||
    def destroy(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        self.notifier.stopReading()
 | 
			
		||||
        self.notifier.loseConnection()
 | 
			
		||||
        if conf.imap_expunge_on_close:
 | 
			
		||||
            l = self.expunge()
 | 
			
		||||
 | 
			
		||||
class MaildirMessagePart(object):
 | 
			
		||||
    implements(imap4.IMessagePart)
 | 
			
		||||
    
 | 
			
		||||
    def __init__(self, message):
 | 
			
		||||
        self.message = message
 | 
			
		||||
        self.data = str(message)
 | 
			
		||||
    
 | 
			
		||||
    def getHeaders(self, negate, *names):
 | 
			
		||||
        if not names:
 | 
			
		||||
            names = self.message.keys()
 | 
			
		||||
 | 
			
		||||
        headers = {}
 | 
			
		||||
        if negate:
 | 
			
		||||
            for header in self.message.keys():
 | 
			
		||||
                if header.upper() not in names:
 | 
			
		||||
                    headers[header.lower()] = self.message.get(header, '')
 | 
			
		||||
        else:
 | 
			
		||||
            for name in names:
 | 
			
		||||
                headers[name.lower()] = self.message.get(name, '')
 | 
			
		||||
 | 
			
		||||
        return headers
 | 
			
		||||
 | 
			
		||||
    def getBodyFile(self):
 | 
			
		||||
        return StringIO(self.message.get_payload())
 | 
			
		||||
    
 | 
			
		||||
    def getSize(self):
 | 
			
		||||
        return len(self.data)
 | 
			
		||||
    
 | 
			
		||||
    def isMultipart(self):
 | 
			
		||||
        return self.message.is_multipart()
 | 
			
		||||
    
 | 
			
		||||
    def getSubPart(self, part):
 | 
			
		||||
        return MaildirMessagePart(self.message.get_payload(part))
 | 
			
		||||
 | 
			
		||||
class MaildirMessage(MaildirMessagePart):
 | 
			
		||||
    implements(imap4.IMessage)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, uid, message, flags, date):
 | 
			
		||||
        MaildirMessagePart.__init__(self, message)
 | 
			
		||||
        self.uid = uid
 | 
			
		||||
        self.message = email.message_from_string(message)
 | 
			
		||||
        self.flags = flags
 | 
			
		||||
        self.date = date
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    def getUID(self):
 | 
			
		||||
        return self.uid
 | 
			
		||||
 | 
			
		||||
    def getFlags(self):
 | 
			
		||||
        return self.flags
 | 
			
		||||
 | 
			
		||||
    def getInternalDate(self):
 | 
			
		||||
        return self.date
 | 
			
		||||
@ -1,15 +0,0 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
MSG_ACTIVE = 0
 | 
			
		||||
MSG_FROZEN = 1
 | 
			
		||||
 | 
			
		||||
IMAP_FLAGS = {
 | 
			
		||||
    'SEEN':     '\\Seen',
 | 
			
		||||
    'FLAGGED':  '\\Flagged',
 | 
			
		||||
    'ANSWERED': '\\Answered',
 | 
			
		||||
    'RECENT':   '\\Recent',
 | 
			
		||||
    'DELETED':  '\\Deleted',
 | 
			
		||||
    'DRAFT':    '\\Draft'    
 | 
			
		||||
    }
 | 
			
		||||
IMAP_HDELIM = '.'
 | 
			
		||||
IMAP_ACC_CONN_NUM = '...ConnectionNumber...'
 | 
			
		||||
IMAP_MBOX_REG = {}
 | 
			
		||||
@ -1,23 +0,0 @@
 | 
			
		||||
######### Запуск приложения под twistd
 | 
			
		||||
 | 
			
		||||
from twisted.application import internet, service
 | 
			
		||||
from somemodule import EchoFactory
 | 
			
		||||
 | 
			
		||||
port = 7001
 | 
			
		||||
factory = EchoFactory()
 | 
			
		||||
 | 
			
		||||
# this is the important bit
 | 
			
		||||
application = service.Application("echo")  # create the Application
 | 
			
		||||
echoService = internet.TCPServer(port, factory) # create the service
 | 
			
		||||
# add the service to the application
 | 
			
		||||
echoService.setServiceParent(application)
 | 
			
		||||
 | 
			
		||||
#############################################
 | 
			
		||||
	def startService(self):
 | 
			
		||||
        service.Service.startService(self)
 | 
			
		||||
 | 
			
		||||
    def stopService(self):
 | 
			
		||||
        service.Service.stopService(self)
 | 
			
		||||
        if self._call:
 | 
			
		||||
            self._call.cancel()
 | 
			
		||||
            self._call = None
 | 
			
		||||
							
								
								
									
										116
									
								
								serpent/queue.py
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								serpent/queue.py
									
									
									
									
									
								
							@ -1,116 +0,0 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
from serpent.config import conf
 | 
			
		||||
from serpent import dataio, misc, rules
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from os import path
 | 
			
		||||
from DNS import dnslookup
 | 
			
		||||
from operator import itemgetter
 | 
			
		||||
from smtplib import SMTP
 | 
			
		||||
 | 
			
		||||
class SmtpQueue(object):
 | 
			
		||||
    def __init__(self, store, local_delivery):
 | 
			
		||||
        self.stor = store
 | 
			
		||||
        self.local_delivery = local_delivery
 | 
			
		||||
 | 
			
		||||
    def add(self, data):
 | 
			
		||||
        '''Ставит письмо в очередь'''
 | 
			
		||||
        data['add_time'] = datetime.utcnow()
 | 
			
		||||
        data['state'] = misc.MSG_ACTIVE
 | 
			
		||||
        w = self.stor.write(data)
 | 
			
		||||
        if not w:
 | 
			
		||||
            return False
 | 
			
		||||
        return self.__process_(data['id'])
 | 
			
		||||
    
 | 
			
		||||
    def run(self):
 | 
			
		||||
        '''Запускает обработку очереди'''
 | 
			
		||||
        now = datetime.utcnow()
 | 
			
		||||
        check_delta = timedelta(minutes = conf.smtp_queue_check_period)
 | 
			
		||||
        expire_delta = timedelta(minutes = conf.smtp_queue_message_ttl)
 | 
			
		||||
        for mid in self.__list_messages_():
 | 
			
		||||
            info = self.stor.getinfo(mid)
 | 
			
		||||
            if (now - info['add_time']) >= expire_delta:
 | 
			
		||||
                self.stor.delete(mid)
 | 
			
		||||
                continue
 | 
			
		||||
            if (now - info['add_time']) >= check_delta:
 | 
			
		||||
                continue
 | 
			
		||||
            self.__process_(mid)
 | 
			
		||||
        return True
 | 
			
		||||
    
 | 
			
		||||
    def __local_deliver_(self, mid):
 | 
			
		||||
        message = self.stor.read(mid)
 | 
			
		||||
        user = rules.username_by_email(message['rcpt'])
 | 
			
		||||
        return self.local_delivery.deliver(user, message['message'])
 | 
			
		||||
    
 | 
			
		||||
    def __send_email_(self, mid):
 | 
			
		||||
        info = self.stor.getinfo(mid)
 | 
			
		||||
        try:
 | 
			
		||||
            mail_servers = dnslookup(info['rcpt'][1], 'mx')
 | 
			
		||||
        except:
 | 
			
		||||
            return False
 | 
			
		||||
        mail_servers = sorted(mail_servers, key=itemgetter(0))
 | 
			
		||||
        for _, mx in mail_servers:
 | 
			
		||||
            s = SMTP(local_hostname = conf.smtp_hostname)
 | 
			
		||||
            try:
 | 
			
		||||
                ret_code, banner = s.connect(mx, 25)
 | 
			
		||||
            except:
 | 
			
		||||
                s.quit()
 | 
			
		||||
                continue
 | 
			
		||||
            if ret_code != 220:
 | 
			
		||||
                s.quit()
 | 
			
		||||
                continue
 | 
			
		||||
            try:
 | 
			
		||||
                s.starttls()
 | 
			
		||||
            except:
 | 
			
		||||
                if conf.smtp_email_tls_required:
 | 
			
		||||
                    s.quit()
 | 
			
		||||
                    continue
 | 
			
		||||
            from_addr = conf.smtp_email_delim.join(info['from'])
 | 
			
		||||
            to_addr = conf.smtp_email_delim.join(info['rcpt'])
 | 
			
		||||
            message = self.stor.read(mid)
 | 
			
		||||
            try:
 | 
			
		||||
                s.sendmail(from_addr, [to_addr], message['message'])
 | 
			
		||||
            except:
 | 
			
		||||
                s.quit()
 | 
			
		||||
                continue
 | 
			
		||||
            s.quit()
 | 
			
		||||
            return True
 | 
			
		||||
        return False
 | 
			
		||||
            
 | 
			
		||||
    
 | 
			
		||||
    def __freeze_(self, mid):
 | 
			
		||||
        info = self.stor.getinfo(mid)
 | 
			
		||||
        if info:
 | 
			
		||||
            if info['state'] != misc.MSG_FROZEN :
 | 
			
		||||
                info['state'] = misc.MSG_FROZEN
 | 
			
		||||
                if self.stor.setinfo(info):
 | 
			
		||||
                    return True
 | 
			
		||||
            else:
 | 
			
		||||
                return True
 | 
			
		||||
        return False
 | 
			
		||||
    
 | 
			
		||||
    def __process_(self, mid):
 | 
			
		||||
        info = self.stor.getinfo(mid)
 | 
			
		||||
        if info:
 | 
			
		||||
            if info['rcpt'][1] in conf.local_domains:
 | 
			
		||||
                if self.__local_deliver_(mid):
 | 
			
		||||
                    self.__remove_message_(mid)
 | 
			
		||||
                    return True
 | 
			
		||||
                else:
 | 
			
		||||
                    return self.__freeze_(mid)
 | 
			
		||||
            else:
 | 
			
		||||
                if self.__send_email_(mid):
 | 
			
		||||
                    self.__remove_message_(mid)
 | 
			
		||||
                    return True
 | 
			
		||||
                else:
 | 
			
		||||
                    return self.__freeze_(mid)
 | 
			
		||||
        return False
 | 
			
		||||
    
 | 
			
		||||
    def __list_messages_(self):
 | 
			
		||||
        return self.stor.list()
 | 
			
		||||
    
 | 
			
		||||
    def __remove_message_(self, mid):
 | 
			
		||||
        self.stor.delete(mid)
 | 
			
		||||
 | 
			
		||||
mailstore = dataio.MailDirStore()
 | 
			
		||||
squeue = SmtpQueue(dataio.SmtpFileStore(path.join(conf.app_dir, conf.smtp_queue_dir)),
 | 
			
		||||
                   mailstore)
 | 
			
		||||
@ -1,36 +0,0 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
from serpent.config import conf
 | 
			
		||||
from serpent.usrpwd import dbs
 | 
			
		||||
from serpent import errors
 | 
			
		||||
'''Здесь находятся функции для проверки различных вещей.'''
 | 
			
		||||
 | 
			
		||||
def validateFrom(obj, email):
 | 
			
		||||
    if not conf.smtp_open_relay:
 | 
			
		||||
        if email[1] in conf.local_domains:
 | 
			
		||||
            if not obj.avatarId:
 | 
			
		||||
                raise errors.SMTPAuthReqError()
 | 
			
		||||
            elif obj.avatarId != username_by_email(email):
 | 
			
		||||
                raise errors.SMTPBadSender(conf.smtp_email_delim.join(email))
 | 
			
		||||
        elif obj.avatarId:
 | 
			
		||||
            raise errors.SMTPBadSender(conf.smtp_email_delim.join(email))
 | 
			
		||||
    return True
 | 
			
		||||
 | 
			
		||||
def validateTo(obj, user):
 | 
			
		||||
    local = user.dest.local
 | 
			
		||||
    domain = user.dest.domain
 | 
			
		||||
    for u, f in user.protocol._to:
 | 
			
		||||
        if local == u.dest.local and domain == u.dest.domain:
 | 
			
		||||
            del user.protocol._to[user.protocol._to.index((u,f))]
 | 
			
		||||
    if domain in conf.local_domains and not username_by_email([local, domain]):
 | 
			
		||||
        raise errors.SMTPBadRcpt(conf.smtp_email_delim.join([local, domain]))
 | 
			
		||||
    if domain not in conf.local_domains and not obj.avatarId and not conf.smtp_open_relay:
 | 
			
		||||
        raise errors.SMTPNotOpenRelay()
 | 
			
		||||
    return True             # Адрес найден в базах пользователей
 | 
			
		||||
    
 | 
			
		||||
def username_by_email(email):
 | 
			
		||||
    result = None
 | 
			
		||||
    for db in dbs:
 | 
			
		||||
        result = db.username_by_email(email)
 | 
			
		||||
        if result:
 | 
			
		||||
            break
 | 
			
		||||
    return result
 | 
			
		||||
@ -1,24 +0,0 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
userdb = {
 | 
			
		||||
         'user1': ['password1', 'local1', 'dom.lan'],
 | 
			
		||||
         'user2': ['password2', 'local2', 'dom.lan']
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
class DictUDB(object):
 | 
			
		||||
    def __init__(self, userdb = userdb):
 | 
			
		||||
        self. userdb = userdb
 | 
			
		||||
    def username_by_email(self, email = None):
 | 
			
		||||
        if not email:
 | 
			
		||||
            return None
 | 
			
		||||
        for usr in self.userdb.keys():
 | 
			
		||||
            if email[0] == self.userdb[usr][1] and email[1] == self.userdb[usr][2]:
 | 
			
		||||
                return usr
 | 
			
		||||
        return False
 | 
			
		||||
    def check_pw(self, creds):
 | 
			
		||||
        usr = creds[0]
 | 
			
		||||
        pwd = creds[1]
 | 
			
		||||
        return usr in self.userdb.keys() and pwd == self.userdb[usr][0]
 | 
			
		||||
    def user_exist(self, username):
 | 
			
		||||
        return username in self.userdb.keys()
 | 
			
		||||
dbs = [DictUDB()]
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user