Compare commits

..

7 Commits

Author SHA1 Message Date
inpos 7318e52b94 zope.interface py3 2016-06-08 20:38:12 +03:00
inpos f2a43a948a mech_imap 2016-06-05 11:28:26 +03:00
inpos de41f90943 Возможность изменять флаги письма 2016-05-31 14:11:38 +03:00
inpos f36515f233 ExtendedMaildir 2016-05-31 13:42:31 +03:00
inpos 7423d4941d 2to3 2016-05-31 10:40:17 +03:00
inpos 44182ddd45 initial 2016-05-31 10:01:40 +03:00
inpos 6812dd0b8d mailbox.py нам не нужен 2016-05-30 17:42:28 +03:00
8 changed files with 95 additions and 477 deletions

View File

@ -6,7 +6,7 @@ from twisted.python import failure
from twisted.internet import reactor, defer from twisted.internet import reactor, defer
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
from zope.interface import implements from zope.interface import implementer
from mech_smtp import SerpentSMTPFactory, smtp_portal from mech_smtp import SerpentSMTPFactory, smtp_portal
from mech_imap import SerpentIMAPFactory, imap_portal from mech_imap import SerpentIMAPFactory, imap_portal
@ -14,10 +14,10 @@ from serpent.usrpwd import dbs
from serpent.queue import squeue from serpent.queue import squeue
from serpent.config import conf from serpent.config import conf
@implementer(ICredentialsChecker)
class CredChecker(object): class CredChecker(object):
'''Класс проверки данных авторизации. '''Класс проверки данных авторизации.
Параметром в конструктор передаётся список (list()) объектов баз пользователей.''' Параметром в конструктор передаётся список (list()) объектов баз пользователей.'''
implements(ICredentialsChecker)
credentialInterfaces = (credentials.IUsernamePassword, credentialInterfaces = (credentials.IUsernamePassword,
credentials.IUsernameHashedPassword) credentials.IUsernameHashedPassword)

View File

@ -2,20 +2,19 @@
import os import os
from zope.interface import implements from zope.interface import implementer
from twisted.cred import portal from twisted.cred import portal
from twisted.internet import protocol, ssl from twisted.internet import protocol, ssl
from twisted.mail import imap4 from twisted.mail import imap4
from serpent.config import conf from serpent.config import conf
from serpent.imap.mailbox import IMAPMailbox from serpent.imap.mailbox import ExtendedMaildir
from serpent.misc import IMAP_HDELIM, IMAP_MBOX_REG, IMAP_ACC_CONN_NUM from serpent.misc import IMAP_HDELIM, IMAP_MBOX_REG, IMAP_ACC_CONN_NUM
from shutil import rmtree, move from shutil import rmtree, move
@implementer(imap4.IAccount)
class IMAPUserAccount(object): class IMAPUserAccount(object):
implements(imap4.IAccount)
def __init__(self, mdir): def __init__(self, mdir):
if not os.path.exists(mdir): if not os.path.exists(mdir):
os.makedirs(mdir) os.makedirs(mdir)
@ -25,21 +24,19 @@ class IMAPUserAccount(object):
else: else:
IMAP_MBOX_REG[self.dir] = {} IMAP_MBOX_REG[self.dir] = {}
IMAP_MBOX_REG[self.dir][IMAP_ACC_CONN_NUM] = 0 IMAP_MBOX_REG[self.dir][IMAP_ACC_CONN_NUM] = 0
for m in conf.imap_auto_mbox.keys(): for m in conf.imap_auto_mbox:
name = m
if isinstance(m, unicode):
m = m.encode('imap4-utf-7')
if m not in IMAP_MBOX_REG[self.dir].keys(): if m not in IMAP_MBOX_REG[self.dir].keys():
IMAP_MBOX_REG[self.dir][m] = self.create(m) if isinstance(m, str):
IMAP_MBOX_REG[self.dir][m].setSpecial(conf.imap_auto_mbox[name]) m = m.encode('imap4-utf-7')
IMAP_MBOX_REG[self.dir][m] = ExtendedMaildir(os.path.join(self.dir, m))
IMAP_MBOX_REG[self.dir][m]._start_monitor() IMAP_MBOX_REG[self.dir][m]._start_monitor()
self.subscribe(m) self.subscribe(m)
def _getMailbox(self, path): def _getMailbox(self, path):
if isinstance(path, unicode): if isinstance(path, str):
path = path.encode('imap4-utf-7') path = path.encode('imap4-utf-7')
fullPath = os.path.join(self.dir, path) fullPath = os.path.join(self.dir, path)
mbox = IMAPMailbox(fullPath) mbox = ExtendedMaildir(fullPath)
mbox._start_monitor() mbox._start_monitor()
return mbox return mbox
@ -48,7 +45,7 @@ class IMAPUserAccount(object):
yield box.decode('imap4-utf-7'), self.create(box) yield box.decode('imap4-utf-7'), self.create(box)
def select(self, path, rw=False): def select(self, path, rw=False):
if isinstance(path, unicode): if isinstance(path, str):
path = path.encode('imap4-utf-7') path = path.encode('imap4-utf-7')
if path in IMAP_MBOX_REG[self.dir].keys(): if path in IMAP_MBOX_REG[self.dir].keys():
return IMAP_MBOX_REG[self.dir][path] return IMAP_MBOX_REG[self.dir][path]
@ -56,7 +53,7 @@ class IMAPUserAccount(object):
if path in os.listdir(self.dir): if path in os.listdir(self.dir):
return self.create(path) return self.create(path)
else: else:
return None raise imap4.NoSuchMailbox(path)
def addMailbox(self, name, mbox = None): def addMailbox(self, name, mbox = None):
if mbox: if mbox:
@ -64,7 +61,7 @@ class IMAPUserAccount(object):
return self.create(name) return self.create(name)
def create(self, pathspec): def create(self, pathspec):
if isinstance(pathspec, unicode): if isinstance(pathspec, str):
pathspec = pathspec.encode('imap4-utf-7') pathspec = pathspec.encode('imap4-utf-7')
if pathspec not in IMAP_MBOX_REG[self.dir].keys(): if pathspec not in IMAP_MBOX_REG[self.dir].keys():
paths = filter(None, pathspec.split(IMAP_HDELIM)) paths = filter(None, pathspec.split(IMAP_HDELIM))
@ -73,29 +70,25 @@ class IMAPUserAccount(object):
if subpath not in IMAP_MBOX_REG[self.dir].keys(): if subpath not in IMAP_MBOX_REG[self.dir].keys():
try: try:
IMAP_MBOX_REG[self.dir][subpath] = self._getMailbox(IMAP_HDELIM.join(paths[:accum])) IMAP_MBOX_REG[self.dir][subpath] = self._getMailbox(IMAP_HDELIM.join(paths[:accum]))
IMAP_MBOX_REG[self.dir][subpath].subscribe()
except imap4.MailboxCollision: except imap4.MailboxCollision:
pass pass
IMAP_MBOX_REG[self.dir][subpath].hasChildren()
IMAP_MBOX_REG[self.dir][pathspec] = self._getMailbox(pathspec) IMAP_MBOX_REG[self.dir][pathspec] = self._getMailbox(pathspec)
IMAP_MBOX_REG[self.dir][pathspec].hasNoChildren()
IMAP_MBOX_REG[self.dir][pathspec].subscribe()
return IMAP_MBOX_REG[self.dir][pathspec] return IMAP_MBOX_REG[self.dir][pathspec]
def delete(self, pathspec): def delete(self, pathspec):
if isinstance(pathspec, unicode): if isinstance(pathspec, str):
pathspec = pathspec.encode('imap4-utf-7') pathspec = pathspec.encode('imap4-utf-7')
if pathspec in conf.imap_auto_mbox.keys(): if pathspec in conf.imap_auto_mbox:
raise imap4.MailboxException, pathspec raise imap4.MailboxException(pathspec)
if pathspec not in IMAP_MBOX_REG[self.dir].keys(): if pathspec not in IMAP_MBOX_REG[self.dir].keys():
raise imap4.NoSuchMailbox, pathspec raise imap4.MailboxException("No such mailbox")
inferiors = self._inferiorNames(pathspec) inferiors = self._inferiorNames(pathspec)
if r'\Noselect' in IMAP_MBOX_REG[self.dir][pathspec].getFlags(): if r'\Noselect' in IMAP_MBOX_REG[self.dir][pathspec].getFlags():
# Check for hierarchically inferior mailboxes with this one # Check for hierarchically inferior mailboxes with this one
# as part of their root. # as part of their root.
for inferior in inferiors: for inferior in inferiors:
if inferior != pathspec: if inferior != pathspec:
raise imap4.MailboxException, "Hierarchically inferior mailboxes exist and \\Noselect is set" raise imap4.MailboxException("Hierarchically inferior mailboxes exist and \\Noselect is set")
for inferior in inferiors: for inferior in inferiors:
mdir = IMAP_MBOX_REG[self.dir][inferior].path mdir = IMAP_MBOX_REG[self.dir][inferior].path
IMAP_MBOX_REG[self.dir][inferior].destroy() IMAP_MBOX_REG[self.dir][inferior].destroy()
@ -104,54 +97,45 @@ class IMAPUserAccount(object):
return True return True
def rename(self, oldname, newname): def rename(self, oldname, newname):
if oldname in conf.imap_auto_mbox.keys(): if oldname in conf.imap_auto_mbox:
raise imap4.MailboxException, oldname raise imap4.MailboxException(oldname)
if isinstance(oldname, unicode): if isinstance(oldname, str):
oldname = oldname.encode('imap4-utf-7') oldname = oldname.encode('imap4-utf-7')
if isinstance(newname, unicode): if isinstance(newname, str):
newname = newname.encode('imap4-utf-7') newname = newname.encode('imap4-utf-7')
if oldname not in IMAP_MBOX_REG[self.dir].keys(): if oldname not in IMAP_MBOX_REG[self.dir].keys():
raise imap4.NoSuchMailbox, oldname raise imap4.NoSuchMailbox(oldname)
inferiors = [(o, o.replace(oldname, newname, 1)) for o in self._inferiorNames(oldname)] inferiors = [(o, o.replace(oldname, newname, 1)) for o in self._inferiorNames(oldname)]
for (old, new) in inferiors: for (old, new) in inferiors:
if new in IMAP_MBOX_REG[self.dir].keys(): if new in IMAP_MBOX_REG[self.dir].keys():
raise imap4.MailboxCollision, new raise imap4.MailboxCollision(new)
for (old, new) in inferiors: for (old, new) in inferiors:
IMAP_MBOX_REG[self.dir][old]._stop_monitor()
move(os.path.join(self.dir, old), os.path.join(self.dir, new)) 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] = 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 = os.path.join(self.dir, new)
IMAP_MBOX_REG[self.dir][new].open_flags() IMAP_MBOX_REG[self.dir][new].path_msg_info = os.path.join(self.dir, conf.imap_msg_info)
IMAP_MBOX_REG[self.dir][new]._start_monitor() 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] del IMAP_MBOX_REG[self.dir][old]
return True return True
def subscribe(self, name): def subscribe(self, name):
if isinstance(name, unicode): if isinstance(name, str):
name = name.encode('imap4-utf-7') name = name.encode('imap4-utf-7')
if name in IMAP_MBOX_REG[self.dir].keys(): if name in IMAP_MBOX_REG[self.dir].keys():
IMAP_MBOX_REG[self.dir][name].subscribe() IMAP_MBOX_REG[self.dir][name].subscribe()
return True
#raise imap4.NoSuchMailbox, name
def unsubscribe(self, name): def unsubscribe(self, name):
if name in conf.imap_auto_mbox.keys(): if name in conf.imap_auto_mbox:
return False raise imap4.MailboxException(name)
# raise imap4.MailboxException, name if isinstance(name, str):
if isinstance(name, unicode):
name = name.encode('imap4-utf-7') name = name.encode('imap4-utf-7')
if name in IMAP_MBOX_REG[self.dir].keys(): if name in IMAP_MBOX_REG[self.dir].keys():
IMAP_MBOX_REG[self.dir][name].unsubscribe() IMAP_MBOX_REG[self.dir][name].unsubscribe()
return True
#raise imap4.NoSuchMailbox, name
def isSubscribed(self, name): def isSubscribed(self, name):
if isinstance(name, unicode): if isinstance(name, str):
name = name.encode('imap4-utf-7') name = name.encode('imap4-utf-7')
if name in IMAP_MBOX_REG[self.dir].keys():
return IMAP_MBOX_REG[self.dir][name].is_subscribed() return IMAP_MBOX_REG[self.dir][name].is_subscribed()
else:
raise imap4.NoSuchMailbox, name
def _inferiorNames(self, name): def _inferiorNames(self, name):
name_l = name.split(IMAP_HDELIM) name_l = name.split(IMAP_HDELIM)
@ -161,9 +145,8 @@ class IMAPUserAccount(object):
inferiors.append(infname) inferiors.append(infname)
return inferiors return inferiors
@implementer(portal.IRealm)
class SerpentIMAPRealm(object): class SerpentIMAPRealm(object):
implements(portal.IRealm)
def requestAvatar(self, avatarId, mind, *interfaces): def requestAvatar(self, avatarId, mind, *interfaces):
if imap4.IAccount not in interfaces: if imap4.IAccount not in interfaces:
raise NotImplementedError( raise NotImplementedError(
@ -176,16 +159,16 @@ class SerpentIMAPRealm(object):
class IMAPServerProtocol(imap4.IMAP4Server): class IMAPServerProtocol(imap4.IMAP4Server):
def lineReceived(self, line): def lineReceived(self, line):
if isinstance(line, unicode): if isinstance(line, str):
line = line.encode('utf-8') line = line.encode('utf-8')
print "CLIENT:", line print("CLIENT:", line)
imap4.IMAP4Server.lineReceived(self, line) imap4.IMAP4Server.lineReceived(self, line)
def sendLine(self, line): def sendLine(self, line):
imap4.IMAP4Server.sendLine(self, line) imap4.IMAP4Server.sendLine(self, line)
if isinstance(line, unicode): if isinstance(line, str):
line = line.encode('utf-8') line = line.encode('utf-8')
print "SERVER:", line print("SERVER:", line)
def connectionLost(self, reason): def connectionLost(self, reason):
self.setTimeout(None) self.setTimeout(None)
@ -201,7 +184,7 @@ class IMAPServerProtocol(imap4.IMAP4Server):
self.account = None self.account = None
def _parseMbox(self, name): def _parseMbox(self, name):
if isinstance(name, unicode): if isinstance(name, str):
return name return name
try: try:
return name.decode('imap4-utf-7') return name.decode('imap4-utf-7')
@ -292,20 +275,12 @@ class IMAPServerProtocol(imap4.IMAP4Server):
def __cbStatus(self, status, tag, box): def __cbStatus(self, status, tag, box):
line = ' '.join(['%s %s' % x for x in status.iteritems()]) line = ' '.join(['%s %s' % x for x in status.iteritems()])
if isinstance(box, unicode): if isinstance(box, str):
box = box.encode('imap4-utf-7') box = box.encode('imap4-utf-7')
self.sendUntaggedResponse('STATUS %s (%s)' % (box, line)) self.sendUntaggedResponse('STATUS %s (%s)' % (box, line))
self.sendPositiveResponse(tag, 'STATUS complete') self.sendPositiveResponse(tag, 'STATUS complete')
def __ebStatus(self, failure, tag, box): def __ebStatus(self, failure, tag, box):
self.sendBadResponse(tag, 'STATUS %s failed: %s' % (box, str(failure.value))) self.sendBadResponse(tag, 'STATUS %s failed: %s' % (box, str(failure.value)))
def _cbListWork(self, mailboxes, tag, sub, cmdName):
for (name, box) in mailboxes:
if not sub or self.account.isSubscribed(name):
flags = box.getMboxFlags()
delim = box.getHierarchicalDelimiter()
resp = (imap4.DontQuoteMe(cmdName), map(imap4.DontQuoteMe, flags), delim, name.encode('imap4-utf-7'))
self.sendUntaggedResponse(imap4.collapseNestedLists(resp))
self.sendPositiveResponse(tag, '%s completed' % (cmdName,))
################################################################################ ################################################################################
@ -316,11 +291,10 @@ class SerpentIMAPFactory(protocol.Factory):
def buildProtocol(self, addr): def buildProtocol(self, addr):
contextFactory = None contextFactory = None
if conf.tls: if conf.tls:
tls_data = file(conf.tls_pem, 'rb').read() tls_data = open(conf.tls_pem, 'rb').read()
cert = ssl.PrivateCertificate.loadPEM(tls_data) cert = ssl.PrivateCertificate.loadPEM(tls_data)
contextFactory = cert.options() contextFactory = cert.options()
p = IMAPServerProtocol(contextFactory = contextFactory) p = IMAPServerProtocol(contextFactory = contextFactory)
p.setTimeout(conf.imap_connection_timeout)
if conf.tls: if conf.tls:
p.canStartTLS = True p.canStartTLS = True
p.IDENT = '%s ready' % conf.SRVNAME p.IDENT = '%s ready' % conf.SRVNAME

View File

@ -5,8 +5,8 @@ from serpent import rules
from serpent.queue import squeue from serpent.queue import squeue
from email.Header import Header from email.header import Header
from zope.interface import implements from zope.interface import implementer
from twisted.internet import defer, ssl from twisted.internet import defer, ssl
from twisted.mail import smtp from twisted.mail import smtp
@ -16,10 +16,8 @@ from twisted.cred.portal import IRealm, Portal
@implementer(smtp.IMessageDelivery)
class SmtpMessageDelivery: class SmtpMessageDelivery:
implements(smtp.IMessageDelivery)
def __init__(self, avatarId = None): def __init__(self, avatarId = None):
self.avatarId = avatarId self.avatarId = avatarId
@ -59,9 +57,8 @@ class SmtpMessageDelivery:
} }
return lambda: SmtpMessage(msg) return lambda: SmtpMessage(msg)
@implementer(smtp.IMessage)
class SmtpMessage: class SmtpMessage:
implements(smtp.IMessage)
def __init__(self, msg): def __init__(self, msg):
self.lines = [] self.lines = []
self.size = 0 self.size = 0
@ -113,7 +110,7 @@ class SerpentSMTPFactory(smtp.SMTPFactory):
def buildProtocol(self, addr): def buildProtocol(self, addr):
contextFactory = None contextFactory = None
if conf.tls: if conf.tls:
tls_data = file(conf.tls_pem, 'rb').read() tls_data = open(conf.tls_pem, 'rb').read()
cert = ssl.PrivateCertificate.loadPEM(tls_data) cert = ssl.PrivateCertificate.loadPEM(tls_data)
contextFactory = cert.options() contextFactory = cert.options()
p = smtp.SMTPFactory.buildProtocol(self, addr) p = smtp.SMTPFactory.buildProtocol(self, addr)
@ -126,10 +123,8 @@ class SerpentSMTPFactory(smtp.SMTPFactory):
return p return p
@implementer(IRealm)
class SmtpRealm: class SmtpRealm:
implements(IRealm)
def requestAvatar(self, avatarId, mind, *interfaces): def requestAvatar(self, avatarId, mind, *interfaces):
if smtp.IMessageDelivery in interfaces: if smtp.IMessageDelivery in interfaces:
return smtp.IMessageDelivery, SmtpMessageDelivery(avatarId), lambda: None return smtp.IMessageDelivery, SmtpMessageDelivery(avatarId), lambda: None

View File

@ -6,10 +6,9 @@ conf = Config()
conf.VERSION = '0.1.0' conf.VERSION = '0.1.0'
conf.SRVNAME = 'Serpent' conf.SRVNAME = 'Serpent'
conf.srv_version = '%s %s' % (conf.SRVNAME, conf.VERSION) conf.srv_version = '%s %s' % (conf.SRVNAME, conf.VERSION)
conf.imap_connection_timeout = 60 * 30
conf.local_domains = ['dom.lan'] # Список доменов, для которых будет приниматься почта conf.local_domains = ['dom.lan'] # Список доменов, для которых будет приниматься почта
conf.tls = True conf.tls = True
conf.tls_pem = u'./serpent.pem' conf.tls_pem = './serpent.pem'
conf.smtp_open_relay = False # Разрешить ли пересылку откуда угодно куда угодно conf.smtp_open_relay = False # Разрешить ли пересылку откуда угодно куда угодно
conf.smtp_email_delim = '@' conf.smtp_email_delim = '@'
conf.smtp_header = '''from [{sender_ip}] (helo={sender_host}) conf.smtp_header = '''from [{sender_ip}] (helo={sender_host})
@ -29,17 +28,9 @@ conf.smtp_email_tls_required = True
conf.imap_SENT = 'Sent' conf.imap_SENT = 'Sent'
conf.imap_TRASH = 'Trash' conf.imap_TRASH = 'Trash'
conf.imap_JUNK = 'Junk' conf.imap_subscribed = '.subscribed'
conf.imap_ARCHIVE = 'Archive'
conf.imap_DRAFTS = 'Drafts'
conf.imap_msg_info = 'msg_info.db' conf.imap_msg_info = 'msg_info.db'
conf.imap_mbox_info = 'mbox_info.db' conf.imap_mbox_info = 'mbox_info.db'
conf.imap_auto_mbox = {'INBOX': '\\INBOX', conf.imap_auto_mbox = ['INBOX', 'Sent', 'Trash']
conf.imap_SENT: '\\Sent',
conf.imap_TRASH: '\\Trash',
conf.imap_JUNK: '\\Junk',
conf.imap_ARCHIVE: '\\Archive',
conf.imap_DRAFTS: '\\Drafts'
}
conf.imap_expunge_on_close = True conf.imap_expunge_on_close = True
conf.imap_check_new_interval = 10.0 # Период проверки новых сообщений в ящике conf.imap_check_new_interval = 10.0 # Период проверки новых сообщений в ящике

View File

@ -4,7 +4,6 @@ import os
import pickle import pickle
from glob import iglob from glob import iglob
from serpent.config import conf from serpent.config import conf
from serpent.misc import IMAP_FLAGS
class SmtpFileStore(object): class SmtpFileStore(object):
def __init__(self, dpath): def __init__(self, dpath):
@ -60,16 +59,17 @@ class SmtpFileStore(object):
class MailDirStore(object): class MailDirStore(object):
def __init__(self): def __init__(self):
from serpent.imap import mailbox import mailbox
self.mbox = mailbox self.mbox = mailbox
def deliver(self, user, message): def deliver(self, user, message):
mdir = os.path.join(conf.app_dir, conf.maildir_user_path % user) mdir = os.path.join(conf.app_dir, conf.maildir_user_path % user)
if not os.path.exists(mdir): if not os.path.exists(mdir):
os.makedirs(mdir) os.makedirs(mdir)
inbox = os.path.join(mdir, 'INBOX') inbox = os.path.join(mdir, 'INBOX')
mailbox = self.mbox.IMAPMailbox(inbox) mailbox = self.mbox.Maildir(inbox)
msg = self.mbox.MaildirMessage(message)
try: try:
mailbox.addMessage(message, [IMAP_FLAGS['RECENT']]) mailbox.add(msg, [])
return True return True
except: except:
raise raise

View File

@ -1,371 +1,37 @@
# -*- coding: utf-8 -*- from mailbox import Maildir
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 StringIO import StringIO
import os import os
from serpent.config import conf class ExtendedMaildir(Maildir):
from serpent import misc def set_flags(self, key, flags):
sflags = sorted(flags)
from sqlitedict import SqliteDict if sflags == self.get_flags(key): return True
subpath = self._lookup(key)
class LoopingTask(Thread): info = '2,' + ''.join(sflags)
def __init__(self, func, event, interval): oldpath = os.path.join(self._path, subpath)
Thread.__init__(self) newsubdir = os.path.split(subpath)[0]
self.func = func newname = key + self.colon + info
self.interval = interval if 'S' in sflags and newsubdir == 'new':
self.stopped = event newsubdir = 'cur'
if 'S' not in sflags and newsubdir == 'cur':
def run(self): newsubdir = 'new'
while not self.stopped.wait(self.interval): newpath = os.path.join(self._path, newsubdir, newname)
self.func() if hasattr(os, 'link'):
os.link(oldpath, newpath)
class SerpentAppendMessageTask(maildir._MaildirMailboxAppendMessageTask): os.remove(oldpath)
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, _):
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.open_flags()
self.lastadded = None
self.__check_flags_()
def open_flags(self):
self.msg_info = SqliteDict(os.path.join(self.path, conf.imap_msg_info))
self.mbox_info = SqliteDict(os.path.join(self.path, conf.imap_mbox_info))
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 _stop_monitor(self):
self.notifier.stopReading()
self.notifier.loseConnection()
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 'flags' not in self.mbox_info.keys(): self.mbox_info['flags'] = []
if 'special' not in self.mbox_info.keys(): self.mbox_info['special'] = ''
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) # XXX
l = [l for l in self.__msg_list_()]
for i in l:
fn = i.split('/')[-1]
if fn not in self.msg_info.keys():
val1 = {'uid': self.getUIDNext()}
if i.split('/')[-2] == 'new':
val1['flags'] = []
else: else:
val1['flags'] = [misc.IMAP_FLAGS['SEEN']] os.rename(oldpath, newpath)
self.msg_info[fn] = val1 self._toc[key] = os.path.join(newsubdir, newname)
#self.msg_info.commit(blocking=False) # XXX def get_flags(self, key):
subpath = self._lookup(key)
def subscribe(self): _, name = os.path.split(subpath)
self.mbox_info['subscribed'] = True info = name.split(self.colon)[-1]
#self.mbox_info.commit(blocking=False) # XXX if info.startswith('2,'):
return info[2:]
def unsubscribe(self):
self.mbox_info['subscribed'] = False
#self.mbox_info.commit(blocking=False) # XXX
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 setSpecial(self, special):
self.mbox_info['special'] = special
#self.mbox_info.commit(blocking=False) # XXX
def getFlags(self):
return sorted(misc.IMAP_FLAGS.values())
def getMboxFlags(self):
f = list(self.mbox_info['flags'])
if self.mbox_info['special'] != '': f.append(self.mbox_info['special'])
return f
def addFlag(self, flag):
self.mbox_info['flags'] = list(set(self.mbox_info['flags']).union([flag]))
#self.mbox_info.commit(blocking=False) # XXX
def removeFlag(self, flag):
self.mbox_info['flags'] = list(set(self.mbox_info['flags']).difference([flag]))
#self.mbox_info.commit(blocking=False) # XXX
def hasChildren(self):
flags = self.getFlags()
if misc.MBOX_FLAGS['HASCHILDREN'] not in flags:
self.addFlag(misc.MBOX_FLAGS['HASCHILDREN'])
if misc.MBOX_FLAGS['HASNOCHILDREN'] in flags:
self.removeFlag(misc.MBOX_FLAGS['HASNOCHILDREN'])
def hasNoChildren(self):
flags = self.getFlags()
if misc.MBOX_FLAGS['HASNOCHILDREN'] not in flags:
self.addFlag(misc.MBOX_FLAGS['HASNOCHILDREN'])
if misc.MBOX_FLAGS['HASCHILDREN'] in flags:
self.removeFlag(misc.MBOX_FLAGS['HASCHILDREN'])
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):
c = 0
for fn in self.msg_info.keys():
if misc.IMAP_FLAGS['RECENT'] in self.msg_info[fn]['flags']:
c += 1
info = self.msg_info[fn]
info['flags'] = set(info['flags']).difference(set([misc.IMAP_FLAGS['RECENT']]))
self.msg_info[fn] = info
#self.msg_info.commit(blocking=False) # XXX
return c
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) # XXX
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) # XXX
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: else:
messagesToFetch = self._seqMessageSetToSeqDict(messages) return ''
return messagesToFetch def add_flag(self, key, flag):
def store(self, messages, flags, mode, uid): self.set_flags(key, ''.join(set(self.get_flags(key)) | set(flag)))
d = {} def remove_flag(self, key, flag):
for _id, path in self.__fetch_(messages, uid).iteritems(): if flag not in self.get_flags(key): return True
filename = path.split('/')[-1] if self.get_flags(key):
if mode < 0: self.set_flags(key, ''.join(set(self.get_flags(key)) - set(flag)))
old_f = self.msg_info[filename]
old_f['flags'] = list(set(old_f['flags']).difference(set(flags)))
self.msg_info[filename] = old_f
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:
old_f = self.msg_info[filename]
old_f['flags'] = flags
self.msg_info[filename] = old_f
elif mode > 0:
old_f = self.msg_info[filename]
old_f['flags'] = list(set(old_f['flags']).union(set(flags)))
self.msg_info[filename] = old_f
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']
#self.msg_info.commit(blocking=False) # XXX
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) # XXX
return uids
def addListener(self, listener):
self.listeners.append(listener)
return True
def removeListener(self, listener):
self.listeners.remove(listener)
return True
def requestStatus(self, names):
return imap4.statusRequestHelper(self, names)
def destroy(self):
pass
def close(self):
print('!!! %s - %d !!!' % (self.path, len(self.listeners)))
if len(self.listeners) == 0:
self._stop_monitor()
if conf.imap_expunge_on_close:
self.expunge()
self.msg_info.commit(blocking=False)
self.mbox_info.commit(blocking = False)
self.msg_info.close()
self.mbox_info.close()
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

View File

@ -3,21 +3,13 @@ MSG_ACTIVE = 0
MSG_FROZEN = 1 MSG_FROZEN = 1
IMAP_FLAGS = { IMAP_FLAGS = {
'SEEN': '\\Seen', 'S': '\\Seen',
'FLAGGED': '\\Flagged', 'F': '\\Flagged',
'ANSWERED': '\\Answered', 'P': '\\Passed',
'RECENT': '\\Recent', 'R': '\\Replied',
'DELETED': '\\Deleted', 'T': '\\Trashed',
'DRAFT': '\\Draft' 'D': '\\Draft'
}
MBOX_FLAGS = {
'NOINFERIORS': '\\Noinferiors',
'NOSELECT': '\\Noselect',
'MARKED': '\\Marked',
'UNMARKED': '\\Unmarked',
'HASCHILDREN': '\\HasChildren',
'HASNOCHILDREN': '\\HasNoChildren'
} }
IMAP_HDELIM = '.' IMAP_HDELIM = '.'
IMAP_ACC_CONN_NUM = '...ConnectionUUID...' IMAP_ACC_CONN_NUM = '...ConnectionNumber...'
IMAP_MBOX_REG = {} IMAP_MBOX_REG = {}

View File

@ -51,7 +51,7 @@ class SmtpQueue(object):
for _, mx in mail_servers: for _, mx in mail_servers:
s = SMTP(local_hostname = conf.smtp_hostname) s = SMTP(local_hostname = conf.smtp_hostname)
try: try:
ret_code, banner = s.connect(mx, 25) ret_code, _ = s.connect(mx, 25)
except: except:
s.quit() s.quit()
continue continue