Compare commits

..

4 Commits

Author SHA1 Message Date
inpos ea035c1e54 uuid 2016-06-10 17:58:10 +03:00
inpos e60d89c1b6 ускорение 2016-06-10 17:31:19 +03:00
inpos 5e23c30797 Флаги 2016-06-10 00:10:51 +03:00
inpos 3b43af2b2f ... 2016-06-08 23:09:12 +03:00
8 changed files with 477 additions and 95 deletions

View File

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

View File

@ -2,19 +2,20 @@
import os
from zope.interface import implementer
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 ExtendedMaildir
from serpent.imap.mailbox import IMAPMailbox
from serpent.misc import IMAP_HDELIM, IMAP_MBOX_REG, IMAP_ACC_CONN_NUM
from shutil import rmtree, move
@implementer(imap4.IAccount)
class IMAPUserAccount(object):
implements(imap4.IAccount)
def __init__(self, mdir):
if not os.path.exists(mdir):
os.makedirs(mdir)
@ -24,19 +25,21 @@ class IMAPUserAccount(object):
else:
IMAP_MBOX_REG[self.dir] = {}
IMAP_MBOX_REG[self.dir][IMAP_ACC_CONN_NUM] = 0
for m in conf.imap_auto_mbox:
for m in conf.imap_auto_mbox.keys():
name = m
if isinstance(m, unicode):
m = m.encode('imap4-utf-7')
if m not in IMAP_MBOX_REG[self.dir].keys():
if isinstance(m, str):
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] = self.create(m)
IMAP_MBOX_REG[self.dir][m].setSpecial(conf.imap_auto_mbox[name])
IMAP_MBOX_REG[self.dir][m]._start_monitor()
self.subscribe(m)
def _getMailbox(self, path):
if isinstance(path, str):
if isinstance(path, unicode):
path = path.encode('imap4-utf-7')
fullPath = os.path.join(self.dir, path)
mbox = ExtendedMaildir(fullPath)
mbox = IMAPMailbox(fullPath)
mbox._start_monitor()
return mbox
@ -45,7 +48,7 @@ class IMAPUserAccount(object):
yield box.decode('imap4-utf-7'), self.create(box)
def select(self, path, rw=False):
if isinstance(path, str):
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]
@ -53,7 +56,7 @@ class IMAPUserAccount(object):
if path in os.listdir(self.dir):
return self.create(path)
else:
raise imap4.NoSuchMailbox(path)
return None
def addMailbox(self, name, mbox = None):
if mbox:
@ -61,7 +64,7 @@ class IMAPUserAccount(object):
return self.create(name)
def create(self, pathspec):
if isinstance(pathspec, str):
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))
@ -70,25 +73,29 @@ class IMAPUserAccount(object):
if subpath not in IMAP_MBOX_REG[self.dir].keys():
try:
IMAP_MBOX_REG[self.dir][subpath] = self._getMailbox(IMAP_HDELIM.join(paths[:accum]))
IMAP_MBOX_REG[self.dir][subpath].subscribe()
except imap4.MailboxCollision:
pass
IMAP_MBOX_REG[self.dir][subpath].hasChildren()
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]
def delete(self, pathspec):
if isinstance(pathspec, str):
if isinstance(pathspec, unicode):
pathspec = pathspec.encode('imap4-utf-7')
if pathspec in conf.imap_auto_mbox:
raise imap4.MailboxException(pathspec)
if pathspec in conf.imap_auto_mbox.keys():
raise imap4.MailboxException, pathspec
if pathspec not in IMAP_MBOX_REG[self.dir].keys():
raise imap4.MailboxException("No such mailbox")
raise imap4.NoSuchMailbox, pathspec
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")
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()
@ -97,45 +104,54 @@ class IMAPUserAccount(object):
return True
def rename(self, oldname, newname):
if oldname in conf.imap_auto_mbox:
raise imap4.MailboxException(oldname)
if isinstance(oldname, str):
if oldname in conf.imap_auto_mbox.keys():
raise imap4.MailboxException, oldname
if isinstance(oldname, unicode):
oldname = oldname.encode('imap4-utf-7')
if isinstance(newname, str):
if isinstance(newname, unicode):
newname = newname.encode('imap4-utf-7')
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)]
for (old, new) in inferiors:
if new in IMAP_MBOX_REG[self.dir].keys():
raise imap4.MailboxCollision(new)
raise imap4.MailboxCollision, new
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))
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)
IMAP_MBOX_REG[self.dir][new].open_flags()
IMAP_MBOX_REG[self.dir][new]._start_monitor()
del IMAP_MBOX_REG[self.dir][old]
return True
def subscribe(self, name):
if isinstance(name, str):
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()
return True
#raise imap4.NoSuchMailbox, name
def unsubscribe(self, name):
if name in conf.imap_auto_mbox:
raise imap4.MailboxException(name)
if isinstance(name, str):
if name in conf.imap_auto_mbox.keys():
return False
# 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()
return True
#raise imap4.NoSuchMailbox, name
def isSubscribed(self, name):
if isinstance(name, str):
if isinstance(name, unicode):
name = name.encode('imap4-utf-7')
return IMAP_MBOX_REG[self.dir][name].is_subscribed()
if name in IMAP_MBOX_REG[self.dir].keys():
return IMAP_MBOX_REG[self.dir][name].is_subscribed()
else:
raise imap4.NoSuchMailbox, name
def _inferiorNames(self, name):
name_l = name.split(IMAP_HDELIM)
@ -145,8 +161,9 @@ class IMAPUserAccount(object):
inferiors.append(infname)
return inferiors
@implementer(portal.IRealm)
class SerpentIMAPRealm(object):
implements(portal.IRealm)
def requestAvatar(self, avatarId, mind, *interfaces):
if imap4.IAccount not in interfaces:
raise NotImplementedError(
@ -159,16 +176,16 @@ class SerpentIMAPRealm(object):
class IMAPServerProtocol(imap4.IMAP4Server):
def lineReceived(self, line):
if isinstance(line, str):
if isinstance(line, unicode):
line = line.encode('utf-8')
print("CLIENT:", line)
print "CLIENT:", line
imap4.IMAP4Server.lineReceived(self, line)
def sendLine(self, line):
imap4.IMAP4Server.sendLine(self, line)
if isinstance(line, str):
if isinstance(line, unicode):
line = line.encode('utf-8')
print("SERVER:", line)
print "SERVER:", line
def connectionLost(self, reason):
self.setTimeout(None)
@ -184,7 +201,7 @@ class IMAPServerProtocol(imap4.IMAP4Server):
self.account = None
def _parseMbox(self, name):
if isinstance(name, str):
if isinstance(name, unicode):
return name
try:
return name.decode('imap4-utf-7')
@ -275,12 +292,20 @@ class IMAPServerProtocol(imap4.IMAP4Server):
def __cbStatus(self, status, tag, box):
line = ' '.join(['%s %s' % x for x in status.iteritems()])
if isinstance(box, str):
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)))
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,))
################################################################################
@ -291,10 +316,11 @@ class SerpentIMAPFactory(protocol.Factory):
def buildProtocol(self, addr):
contextFactory = None
if conf.tls:
tls_data = open(conf.tls_pem, 'rb').read()
tls_data = file(conf.tls_pem, 'rb').read()
cert = ssl.PrivateCertificate.loadPEM(tls_data)
contextFactory = cert.options()
p = IMAPServerProtocol(contextFactory = contextFactory)
p.setTimeout(conf.imap_connection_timeout)
if conf.tls:
p.canStartTLS = True
p.IDENT = '%s ready' % conf.SRVNAME

View File

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

View File

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

View File

@ -4,6 +4,7 @@ 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):
@ -59,17 +60,16 @@ class SmtpFileStore(object):
class MailDirStore(object):
def __init__(self):
import mailbox
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.Maildir(inbox)
msg = self.mbox.MaildirMessage(message)
mailbox = self.mbox.IMAPMailbox(inbox)
try:
mailbox.add(msg, [])
mailbox.addMessage(message, [IMAP_FLAGS['RECENT']])
return True
except:
raise

View File

@ -1,37 +1,371 @@
from mailbox import Maildir
# -*- 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 StringIO import StringIO
import os
class ExtendedMaildir(Maildir):
def set_flags(self, key, flags):
sflags = sorted(flags)
if sflags == self.get_flags(key): return True
subpath = self._lookup(key)
info = '2,' + ''.join(sflags)
oldpath = os.path.join(self._path, subpath)
newsubdir = os.path.split(subpath)[0]
newname = key + self.colon + info
if 'S' in sflags and newsubdir == 'new':
newsubdir = 'cur'
if 'S' not in sflags and newsubdir == 'cur':
newsubdir = 'new'
newpath = os.path.join(self._path, newsubdir, newname)
if hasattr(os, 'link'):
os.link(oldpath, newpath)
os.remove(oldpath)
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, _):
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:
val1['flags'] = [misc.IMAP_FLAGS['SEEN']]
self.msg_info[fn] = val1
#self.msg_info.commit(blocking=False) # XXX
def subscribe(self):
self.mbox_info['subscribed'] = True
#self.mbox_info.commit(blocking=False) # XXX
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:
os.rename(oldpath, newpath)
self._toc[key] = os.path.join(newsubdir, newname)
def get_flags(self, key):
subpath = self._lookup(key)
_, name = os.path.split(subpath)
info = name.split(self.colon)[-1]
if info.startswith('2,'):
return info[2:]
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]
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:
return ''
def add_flag(self, key, flag):
self.set_flags(key, ''.join(set(self.get_flags(key)) | set(flag)))
def remove_flag(self, key, flag):
if flag not in self.get_flags(key): return True
if self.get_flags(key):
self.set_flags(key, ''.join(set(self.get_flags(key)) - set(flag)))
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,13 +3,21 @@ MSG_ACTIVE = 0
MSG_FROZEN = 1
IMAP_FLAGS = {
'S': '\\Seen',
'F': '\\Flagged',
'P': '\\Passed',
'R': '\\Replied',
'T': '\\Trashed',
'D': '\\Draft'
'SEEN': '\\Seen',
'FLAGGED': '\\Flagged',
'ANSWERED': '\\Answered',
'RECENT': '\\Recent',
'DELETED': '\\Deleted',
'DRAFT': '\\Draft'
}
MBOX_FLAGS = {
'NOINFERIORS': '\\Noinferiors',
'NOSELECT': '\\Noselect',
'MARKED': '\\Marked',
'UNMARKED': '\\Unmarked',
'HASCHILDREN': '\\HasChildren',
'HASNOCHILDREN': '\\HasNoChildren'
}
IMAP_HDELIM = '.'
IMAP_ACC_CONN_NUM = '...ConnectionNumber...'
IMAP_ACC_CONN_NUM = '...ConnectionUUID...'
IMAP_MBOX_REG = {}

View File

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