Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
inpos | 7318e52b94 | |
inpos | f2a43a948a | |
inpos | de41f90943 | |
inpos | f36515f233 | |
inpos | 7423d4941d | |
inpos | 44182ddd45 | |
inpos | 6812dd0b8d |
4
main.py
4
main.py
|
@ -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)
|
||||||
|
|
||||||
|
|
81
mech_imap.py
81
mech_imap.py
|
@ -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)
|
||||||
|
@ -27,17 +26,17 @@ class IMAPUserAccount(object):
|
||||||
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:
|
for m in conf.imap_auto_mbox:
|
||||||
if m not in IMAP_MBOX_REG[self.dir].keys():
|
if m not in IMAP_MBOX_REG[self.dir].keys():
|
||||||
if isinstance(m, unicode):
|
if isinstance(m, str):
|
||||||
m = m.encode('imap4-utf-7')
|
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] = 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
|
||||||
|
|
||||||
|
@ -46,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]
|
||||||
|
@ -54,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:
|
||||||
raise imap4.NoSuchMailbox, path
|
raise imap4.NoSuchMailbox(path)
|
||||||
|
|
||||||
def addMailbox(self, name, mbox = None):
|
def addMailbox(self, name, mbox = None):
|
||||||
if mbox:
|
if mbox:
|
||||||
|
@ -62,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))
|
||||||
|
@ -77,19 +76,19 @@ class IMAPUserAccount(object):
|
||||||
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:
|
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()
|
||||||
|
@ -99,47 +98,42 @@ class IMAPUserAccount(object):
|
||||||
|
|
||||||
def rename(self, oldname, newname):
|
def rename(self, oldname, newname):
|
||||||
if oldname in conf.imap_auto_mbox:
|
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:
|
||||||
m = IMAP_MBOX_REG[self.dir][old]
|
|
||||||
del IMAP_MBOX_REG[self.dir][old]
|
|
||||||
for l in m.listeners: m.listeners.remove(l)
|
|
||||||
m.close()
|
|
||||||
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] = self._getMailbox(new)
|
IMAP_MBOX_REG[self.dir][new] = IMAP_MBOX_REG[self.dir][old]
|
||||||
IMAP_MBOX_REG[self.dir][new].subscribe()
|
IMAP_MBOX_REG[self.dir][new].path = os.path.join(self.dir, new)
|
||||||
return IMAP_MBOX_REG[self.dir][newname]
|
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):
|
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:
|
if name in conf.imap_auto_mbox:
|
||||||
raise imap4.MailboxException, name
|
raise imap4.MailboxException(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].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')
|
||||||
return IMAP_MBOX_REG[self.dir][name].is_subscribed()
|
return IMAP_MBOX_REG[self.dir][name].is_subscribed()
|
||||||
|
|
||||||
|
@ -151,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(
|
||||||
|
@ -166,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)
|
||||||
|
@ -191,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')
|
||||||
|
@ -282,7 +275,7 @@ 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')
|
||||||
|
@ -298,7 +291,7 @@ 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)
|
||||||
|
|
17
mech_smtp.py
17
mech_smtp.py
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,317 +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.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 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)
|
|
||||||
|
|
||||||
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:
|
else:
|
||||||
messagesToFetch = self._seqMessageSetToSeqDict(messages)
|
os.rename(oldpath, newpath)
|
||||||
return messagesToFetch
|
self._toc[key] = os.path.join(newsubdir, newname)
|
||||||
def store(self, messages, flags, mode, uid):
|
def get_flags(self, key):
|
||||||
d = {}
|
subpath = self._lookup(key)
|
||||||
for _id, path in self.__fetch_(messages, uid).iteritems():
|
_, name = os.path.split(subpath)
|
||||||
filename = path.split('/')[-1]
|
info = name.split(self.colon)[-1]
|
||||||
if mode < 0:
|
if info.startswith('2,'):
|
||||||
old_f = self.msg_info[filename]['flags']
|
return info[2:]
|
||||||
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']
|
|
||||||
self.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)
|
|
||||||
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):
|
|
||||||
self.notifier.stopReading()
|
|
||||||
self.notifier.loseConnection()
|
|
||||||
if conf.imap_expunge_on_close:
|
|
||||||
self.expunge()
|
|
||||||
self.mbox_info.close()
|
|
||||||
self.msg_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:
|
else:
|
||||||
for name in names:
|
return ''
|
||||||
headers[name.lower()] = self.message.get(name, '')
|
def add_flag(self, key, flag):
|
||||||
|
self.set_flags(key, ''.join(set(self.get_flags(key)) | set(flag)))
|
||||||
return headers
|
def remove_flag(self, key, flag):
|
||||||
|
if flag not in self.get_flags(key): return True
|
||||||
def getBodyFile(self):
|
if self.get_flags(key):
|
||||||
return StringIO(self.message.get_payload())
|
self.set_flags(key, ''.join(set(self.get_flags(key)) - set(flag)))
|
||||||
|
|
||||||
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
|
|
|
@ -3,12 +3,12 @@ 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'
|
||||||
}
|
}
|
||||||
IMAP_HDELIM = '.'
|
IMAP_HDELIM = '.'
|
||||||
IMAP_ACC_CONN_NUM = '...ConnectionNumber...'
|
IMAP_ACC_CONN_NUM = '...ConnectionNumber...'
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue