From 8b8b8c85fdd248dc582d046a597810ba026357a9 Mon Sep 17 00:00:00 2001 From: inpos Date: Sun, 29 May 2016 22:41:48 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D1=87=D0=B8=D1=81=D1=82=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 59 -------- mech_imap.py | 305 -------------------------------------- mech_smtp.py | 140 ----------------- serpent/__init__.py | 0 serpent/config.py | 36 ----- serpent/dataio.py | 76 ---------- serpent/errors.py | 15 -- serpent/imap/__init__.py | 0 serpent/imap/mailbox.py | 313 --------------------------------------- serpent/misc.py | 15 -- serpent/notes | 23 --- serpent/queue.py | 116 --------------- serpent/rules.py | 36 ----- serpent/usrpwd.py | 24 --- 14 files changed, 1158 deletions(-) delete mode 100644 main.py delete mode 100644 mech_imap.py delete mode 100644 mech_smtp.py delete mode 100644 serpent/__init__.py delete mode 100644 serpent/config.py delete mode 100644 serpent/dataio.py delete mode 100644 serpent/errors.py delete mode 100644 serpent/imap/__init__.py delete mode 100644 serpent/imap/mailbox.py delete mode 100644 serpent/misc.py delete mode 100644 serpent/notes delete mode 100644 serpent/queue.py delete mode 100644 serpent/rules.py delete mode 100644 serpent/usrpwd.py diff --git a/main.py b/main.py deleted file mode 100644 index b8616db..0000000 --- a/main.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- 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() \ No newline at end of file diff --git a/mech_imap.py b/mech_imap.py deleted file mode 100644 index fb245de..0000000 --- a/mech_imap.py +++ /dev/null @@ -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()) diff --git a/mech_smtp.py b/mech_smtp.py deleted file mode 100644 index 95aa7a4..0000000 --- a/mech_smtp.py +++ /dev/null @@ -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()) diff --git a/serpent/__init__.py b/serpent/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/serpent/config.py b/serpent/config.py deleted file mode 100644 index f365764..0000000 --- a/serpent/config.py +++ /dev/null @@ -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 # Период проверки новых сообщений в ящике \ No newline at end of file diff --git a/serpent/dataio.py b/serpent/dataio.py deleted file mode 100644 index b809dec..0000000 --- a/serpent/dataio.py +++ /dev/null @@ -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 - \ No newline at end of file diff --git a/serpent/errors.py b/serpent/errors.py deleted file mode 100644 index 18c4924..0000000 --- a/serpent/errors.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/serpent/imap/__init__.py b/serpent/imap/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/serpent/imap/mailbox.py b/serpent/imap/mailbox.py deleted file mode 100644 index ef14309..0000000 --- a/serpent/imap/mailbox.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/serpent/misc.py b/serpent/misc.py deleted file mode 100644 index f8960d0..0000000 --- a/serpent/misc.py +++ /dev/null @@ -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 = {} \ No newline at end of file diff --git a/serpent/notes b/serpent/notes deleted file mode 100644 index 7305d59..0000000 --- a/serpent/notes +++ /dev/null @@ -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 diff --git a/serpent/queue.py b/serpent/queue.py deleted file mode 100644 index d827e8b..0000000 --- a/serpent/queue.py +++ /dev/null @@ -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) diff --git a/serpent/rules.py b/serpent/rules.py deleted file mode 100644 index 6c1a509..0000000 --- a/serpent/rules.py +++ /dev/null @@ -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 diff --git a/serpent/usrpwd.py b/serpent/usrpwd.py deleted file mode 100644 index d0f407d..0000000 --- a/serpent/usrpwd.py +++ /dev/null @@ -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()] \ No newline at end of file