123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944 |
- # -*- test-case-name: twisted.mail.test.test_mail -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Maildir-style mailbox support.
- """
- import os
- import stat
- import socket
- from hashlib import md5
- from zope.interface import implementer
- try:
- import cStringIO as StringIO
- except ImportError:
- import StringIO
- from twisted.mail import pop3
- from twisted.mail import smtp
- from twisted.protocols import basic
- from twisted.persisted import dirdbm
- from twisted.python import log, failure
- from twisted.mail import mail
- from twisted.internet import interfaces, defer, reactor
- from twisted.cred import portal, credentials, checkers
- from twisted.cred.error import UnauthorizedLogin
- INTERNAL_ERROR = '''\
- From: Twisted.mail Internals
- Subject: An Error Occurred
- An internal server error has occurred. Please contact the
- server administrator.
- '''
- class _MaildirNameGenerator:
- """
- A utility class to generate a unique maildir name.
- @type n: L{int}
- @ivar n: A counter used to generate unique integers.
- @type p: L{int}
- @ivar p: The ID of the current process.
- @type s: L{bytes}
- @ivar s: A representation of the hostname.
- @ivar _clock: See C{clock} parameter of L{__init__}.
- """
- n = 0
- p = os.getpid()
- s = socket.gethostname().replace('/', r'\057').replace(':', r'\072')
- def __init__(self, clock):
- """
- @type clock: L{IReactorTime <interfaces.IReactorTime>} provider
- @param clock: A reactor which will be used to learn the current time.
- """
- self._clock = clock
- def generate(self):
- """
- Generate a string which is intended to be unique across all calls to
- this function (across all processes, reboots, etc).
- Strings returned by earlier calls to this method will compare less
- than strings returned by later calls as long as the clock provided
- doesn't go backwards.
- @rtype: L{bytes}
- @return: A unique string.
- """
- self.n = self.n + 1
- t = self._clock.seconds()
- seconds = str(int(t))
- microseconds = '%07d' % (int((t - int(t)) * 10e6),)
- return '%s.M%sP%sQ%s.%s' % (seconds, microseconds,
- self.p, self.n, self.s)
- _generateMaildirName = _MaildirNameGenerator(reactor).generate
- def initializeMaildir(dir):
- """
- Create a maildir user directory if it doesn't already exist.
- @type dir: L{bytes}
- @param dir: The path name for a user directory.
- """
- if not os.path.isdir(dir):
- os.mkdir(dir, 0o700)
- for subdir in ['new', 'cur', 'tmp', '.Trash']:
- os.mkdir(os.path.join(dir, subdir), 0o700)
- for subdir in ['new', 'cur', 'tmp']:
- os.mkdir(os.path.join(dir, '.Trash', subdir), 0o700)
- # touch
- open(os.path.join(dir, '.Trash', 'maildirfolder'), 'w').close()
- class MaildirMessage(mail.FileMessage):
- """
- A message receiver which adds a header and delivers a message to a file
- whose name includes the size of the message.
- @type size: L{int}
- @ivar size: The number of octets in the message.
- """
- size = None
- def __init__(self, address, fp, *a, **kw):
- """
- @type address: L{bytes}
- @param address: The address of the message recipient.
- @type fp: file-like object
- @param fp: The file in which to store the message while it is being
- received.
- @type a: 2-L{tuple} of (0) L{bytes}, (1) L{bytes}
- @param a: Positional arguments for L{FileMessage.__init__}.
- @type kw: L{dict}
- @param kw: Keyword arguments for L{FileMessage.__init__}.
- """
- header = "Delivered-To: %s\n" % address
- fp.write(header)
- self.size = len(header)
- mail.FileMessage.__init__(self, fp, *a, **kw)
- def lineReceived(self, line):
- """
- Write a line to the file.
- @type line: L{bytes}
- @param line: A received line.
- """
- mail.FileMessage.lineReceived(self, line)
- self.size += len(line)+1
- def eomReceived(self):
- """
- At the end of message, rename the file holding the message to its final
- name concatenated with the size of the file.
- @rtype: L{Deferred <defer.Deferred>} which successfully results in
- L{bytes}
- @return: A deferred which returns the name of the file holding the
- message.
- """
- self.finalName = self.finalName+',S=%d' % self.size
- return mail.FileMessage.eomReceived(self)
- @implementer(mail.IAliasableDomain)
- class AbstractMaildirDomain:
- """
- An abstract maildir-backed domain.
- @type alias: L{None} or L{dict} mapping
- L{bytes} to L{AliasBase}
- @ivar alias: A mapping of username to alias.
- @ivar root: See L{__init__}.
- """
- alias = None
- root = None
- def __init__(self, service, root):
- """
- @type service: L{MailService}
- @param service: An email service.
- @type root: L{bytes}
- @param root: The maildir root directory.
- """
- self.root = root
- def userDirectory(self, user):
- """
- Return the maildir directory for a user.
- @type user: L{bytes}
- @param user: A username.
- @rtype: L{bytes} or L{None}
- @return: The user's mail directory for a valid user. Otherwise,
- L{None}.
- """
- return None
- def setAliasGroup(self, alias):
- """
- Set the group of defined aliases for this domain.
- @type alias: L{dict} mapping L{bytes} to L{IAlias} provider.
- @param alias: A mapping of domain name to alias.
- """
- self.alias = alias
- def exists(self, user, memo=None):
- """
- Check whether a user exists in this domain or an alias of it.
- @type user: L{User}
- @param user: A user.
- @type memo: L{None} or L{dict} of L{AliasBase}
- @param memo: A record of the addresses already considered while
- resolving aliases. The default value should be used by all
- external code.
- @rtype: no-argument callable which returns L{IMessage <smtp.IMessage>}
- provider.
- @return: A function which takes no arguments and returns a message
- receiver for the user.
- @raises SMTPBadRcpt: When the given user does not exist in this domain
- or an alias of it.
- """
- if self.userDirectory(user.dest.local) is not None:
- return lambda: self.startMessage(user)
- try:
- a = self.alias[user.dest.local]
- except:
- raise smtp.SMTPBadRcpt(user)
- else:
- aliases = a.resolve(self.alias, memo)
- if aliases:
- return lambda: aliases
- log.err("Bad alias configuration: " + str(user))
- raise smtp.SMTPBadRcpt(user)
- def startMessage(self, user):
- """
- Create a maildir message for a user.
- @type user: L{bytes}
- @param user: A username.
- @rtype: L{MaildirMessage}
- @return: A message receiver for this user.
- """
- if isinstance(user, str):
- name, domain = user.split('@', 1)
- else:
- name, domain = user.dest.local, user.dest.domain
- dir = self.userDirectory(name)
- fname = _generateMaildirName()
- filename = os.path.join(dir, 'tmp', fname)
- fp = open(filename, 'w')
- return MaildirMessage('%s@%s' % (name, domain), fp, filename,
- os.path.join(dir, 'new', fname))
- def willRelay(self, user, protocol):
- """
- Check whether this domain will relay.
- @type user: L{Address}
- @param user: The destination address.
- @type protocol: L{SMTP}
- @param protocol: The protocol over which the message to be relayed is
- being received.
- @rtype: L{bool}
- @return: An indication of whether this domain will relay the message to
- the destination.
- """
- return False
- def addUser(self, user, password):
- """
- Add a user to this domain.
- Subclasses should override this method.
- @type user: L{bytes}
- @param user: A username.
- @type password: L{bytes}
- @param password: A password.
- """
- raise NotImplementedError
- def getCredentialsCheckers(self):
- """
- Return credentials checkers for this domain.
- Subclasses should override this method.
- @rtype: L{list} of L{ICredentialsChecker
- <checkers.ICredentialsChecker>} provider
- @return: Credentials checkers for this domain.
- """
- raise NotImplementedError
- @implementer(interfaces.IConsumer)
- class _MaildirMailboxAppendMessageTask:
- """
- A task which adds a message to a maildir mailbox.
- @ivar mbox: See L{__init__}.
- @type defer: L{Deferred <defer.Deferred>} which successfully returns
- L{None}
- @ivar defer: A deferred which fires when the task has completed.
- @type opencall: L{IDelayedCall <interfaces.IDelayedCall>} provider or
- L{None}
- @ivar opencall: A scheduled call to L{prodProducer}.
- @type msg: file-like object
- @ivar msg: The message to add.
- @type tmpname: L{bytes}
- @ivar tmpname: The pathname of the temporary file holding the message while
- it is being transferred.
- @type fh: file
- @ivar fh: The new maildir file.
- @type filesender: L{FileSender <basic.FileSender>}
- @ivar filesender: A file sender which sends the message.
- @type myproducer: L{IProducer <interfaces.IProducer>}
- @ivar myproducer: The registered producer.
- @type streaming: L{bool}
- @ivar streaming: Indicates whether the registered producer provides a
- streaming interface.
- """
- osopen = staticmethod(os.open)
- oswrite = staticmethod(os.write)
- osclose = staticmethod(os.close)
- osrename = staticmethod(os.rename)
- def __init__(self, mbox, msg):
- """
- @type mbox: L{MaildirMailbox}
- @param mbox: A maildir mailbox.
- @type msg: L{bytes} or file-like object
- @param msg: The message to add.
- """
- self.mbox = mbox
- self.defer = defer.Deferred()
- self.openCall = None
- if not hasattr(msg, "read"):
- msg = StringIO.StringIO(msg)
- self.msg = msg
- def startUp(self):
- """
- Start transferring the message to the mailbox.
- """
- self.createTempFile()
- if self.fh != -1:
- self.filesender = basic.FileSender()
- self.filesender.beginFileTransfer(self.msg, self)
- def registerProducer(self, producer, streaming):
- """
- Register a producer and start asking it for data if it is
- non-streaming.
- @type producer: L{IProducer <interfaces.IProducer>}
- @param producer: A producer.
- @type streaming: L{bool}
- @param streaming: A flag indicating whether the producer provides a
- streaming interface.
- """
- self.myproducer = producer
- self.streaming = streaming
- if not streaming:
- self.prodProducer()
- def prodProducer(self):
- """
- Repeatedly prod a non-streaming producer to produce data.
- """
- self.openCall = None
- if self.myproducer is not None:
- self.openCall = reactor.callLater(0, self.prodProducer)
- self.myproducer.resumeProducing()
- def unregisterProducer(self):
- """
- Finish transferring the message to the mailbox.
- """
- self.myproducer = None
- self.streaming = None
- self.osclose(self.fh)
- self.moveFileToNew()
- def write(self, data):
- """
- Write data to the maildir file.
- @type data: L{bytes}
- @param data: Data to be written to the file.
- """
- try:
- self.oswrite(self.fh, data)
- except:
- self.fail()
- def fail(self, err=None):
- """
- Fire the deferred to indicate the task completed with a failure.
- @type err: L{Failure <failure.Failure>}
- @param err: The error that occurred.
- """
- if err is None:
- err = failure.Failure()
- if self.openCall is not None:
- self.openCall.cancel()
- self.defer.errback(err)
- self.defer = None
- def moveFileToNew(self):
- """
- Place the message in the I{new/} directory, add it to the mailbox and
- fire the deferred to indicate that the task has completed
- successfully.
- """
- while True:
- newname = os.path.join(self.mbox.path, "new", _generateMaildirName())
- try:
- self.osrename(self.tmpname, newname)
- break
- except OSError as e:
- (err, estr) = e.args
- 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.list.append(newname)
- self.defer.callback(None)
- self.defer = None
- def createTempFile(self):
- """
- Create a temporary file to hold the message as it is being transferred.
- """
- attr = (os.O_RDWR | os.O_CREAT | os.O_EXCL
- | getattr(os, "O_NOINHERIT", 0)
- | getattr(os, "O_NOFOLLOW", 0))
- tries = 0
- self.fh = -1
- while True:
- self.tmpname = os.path.join(self.mbox.path, "tmp", _generateMaildirName())
- try:
- self.fh = self.osopen(self.tmpname, attr, 0o600)
- return None
- except OSError:
- tries += 1
- if tries > 500:
- self.defer.errback(RuntimeError("Could not create tmp file for %s" % self.mbox.path))
- self.defer = None
- return None
- class MaildirMailbox(pop3.Mailbox):
- """
- A maildir-backed mailbox.
- @ivar path: See L{__init__}.
- @type list: L{list} of L{int} or 2-L{tuple} of (0) file-like object,
- (1) L{bytes}
- @ivar list: Information about the messages in the mailbox. For undeleted
- messages, the file containing the message and the
- full path name of the file are stored. Deleted messages are indicated
- by 0.
- @type deleted: L{dict} mapping 2-L{tuple} of (0) file-like object,
- (1) L{bytes} to L{bytes}
- @type deleted: A mapping of the information about a file before it was
- deleted to the full path name of the deleted file in the I{.Trash/}
- subfolder.
- """
- AppendFactory = _MaildirMailboxAppendMessageTask
- def __init__(self, path):
- """
- @type path: L{bytes}
- @param path: The directory name for a maildir mailbox.
- """
- self.path = path
- self.list = []
- self.deleted = {}
- initializeMaildir(path)
- for name in ('cur', 'new'):
- for file in os.listdir(os.path.join(path, name)):
- self.list.append((file, os.path.join(path, name, file)))
- self.list.sort()
- self.list = [e[1] for e in self.list]
- def listMessages(self, i=None):
- """
- Retrieve the size of a message, or, if none is specified, the size of
- each message in the mailbox.
- @type i: L{int} or L{None}
- @param i: The 0-based index of a message.
- @rtype: L{int} or L{list} of L{int}
- @return: The number of octets in the specified message, or, if an index
- is not specified, a list of the number of octets for all messages
- in the mailbox. Any value which corresponds to a deleted message
- is set to 0.
- @raise IndexError: When the index does not correspond to a message in
- the mailbox.
- """
- if i is None:
- ret = []
- for mess in self.list:
- if mess:
- ret.append(os.stat(mess)[stat.ST_SIZE])
- else:
- ret.append(0)
- return ret
- return self.list[i] and os.stat(self.list[i])[stat.ST_SIZE] or 0
- def getMessage(self, i):
- """
- Retrieve a file-like object with the contents of a message.
- @type i: L{int}
- @param i: The 0-based index of a message.
- @rtype: file-like object
- @return: A file containing the message.
- @raise IndexError: When the index does not correspond to a message in
- the mailbox.
- """
- return open(self.list[i])
- def getUidl(self, i):
- """
- Get a unique identifier for a message.
- @type i: L{int}
- @param i: The 0-based index of a message.
- @rtype: L{bytes}
- @return: A string of printable characters uniquely identifying the
- message for all time.
- @raise IndexError: When the index does not correspond to a message in
- the mailbox.
- """
- # Returning the actual filename is a mistake. Hash it.
- base = os.path.basename(self.list[i])
- return md5(base).hexdigest()
- def deleteMessage(self, i):
- """
- Mark a message for deletion.
- Move the message to the I{.Trash/} subfolder so it can be undeleted
- by an administrator.
- @type i: L{int}
- @param i: The 0-based index of a message.
- @raise IndexError: When the index does not correspond to a message in
- the mailbox.
- """
- trashFile = os.path.join(
- self.path, '.Trash', 'cur', os.path.basename(self.list[i])
- )
- os.rename(self.list[i], trashFile)
- self.deleted[self.list[i]] = trashFile
- self.list[i] = 0
- def undeleteMessages(self):
- """
- Undelete all messages marked for deletion.
- Move each message marked for deletion from the I{.Trash/} subfolder back
- to its original position.
- """
- for (real, trash) in self.deleted.items():
- try:
- os.rename(trash, real)
- except OSError as e:
- (err, estr) = e.args
- import errno
- # If the file has been deleted from disk, oh well!
- if err != errno.ENOENT:
- raise
- # This is a pass
- else:
- try:
- self.list[self.list.index(0)] = real
- except ValueError:
- self.list.append(real)
- self.deleted.clear()
- def appendMessage(self, txt):
- """
- Add a message to the mailbox.
- @type txt: L{bytes} or file-like object
- @param txt: A message to add.
- @rtype: L{Deferred <defer.Deferred>}
- @return: A deferred which fires when the message has been added to
- the mailbox.
- """
- task = self.AppendFactory(self, txt)
- result = task.defer
- task.startUp()
- return result
- @implementer(pop3.IMailbox)
- class StringListMailbox:
- """
- An in-memory mailbox.
- @ivar msgs: See L{__init__}.
- @type _delete: L{set} of L{int}
- @ivar _delete: The indices of messages which have been marked for deletion.
- """
- def __init__(self, msgs):
- """
- @type msgs: L{list} of L{bytes}
- @param msgs: The contents of each message in the mailbox.
- """
- self.msgs = msgs
- self._delete = set()
- def listMessages(self, i=None):
- """
- Retrieve the size of a message, or, if none is specified, the size of
- each message in the mailbox.
- @type i: L{int} or L{None}
- @param i: The 0-based index of a message.
- @rtype: L{int} or L{list} of L{int}
- @return: The number of octets in the specified message, or, if an index
- is not specified, a list of the number of octets in each message in
- the mailbox. Any value which corresponds to a deleted message is
- set to 0.
- @raise IndexError: When the index does not correspond to a message in
- the mailbox.
- """
- if i is None:
- return [self.listMessages(msg) for msg in range(len(self.msgs))]
- if i in self._delete:
- return 0
- return len(self.msgs[i])
- def getMessage(self, i):
- """
- Return an in-memory file-like object with the contents of a message.
- @type i: L{int}
- @param i: The 0-based index of a message.
- @rtype: L{StringIO <cStringIO.StringIO>}
- @return: An in-memory file-like object containing the message.
- @raise IndexError: When the index does not correspond to a message in
- the mailbox.
- """
- return StringIO.StringIO(self.msgs[i])
- def getUidl(self, i):
- """
- Get a unique identifier for a message.
- @type i: L{int}
- @param i: The 0-based index of a message.
- @rtype: L{bytes}
- @return: A hash of the contents of the message at the given index.
- @raise IndexError: When the index does not correspond to a message in
- the mailbox.
- """
- return md5(self.msgs[i]).hexdigest()
- def deleteMessage(self, i):
- """
- Mark a message for deletion.
- @type i: L{int}
- @param i: The 0-based index of a message to delete.
- @raise IndexError: When the index does not correspond to a message in
- the mailbox.
- """
- self._delete.add(i)
- def undeleteMessages(self):
- """
- Undelete any messages which have been marked for deletion.
- """
- self._delete = set()
- def sync(self):
- """
- Discard the contents of any messages marked for deletion.
- """
- for index in self._delete:
- self.msgs[index] = ""
- self._delete = set()
- @implementer(portal.IRealm)
- class MaildirDirdbmDomain(AbstractMaildirDomain):
- """
- A maildir-backed domain where membership is checked with a
- L{DirDBM <dirdbm.DirDBM>} database.
- The directory structure of a MaildirDirdbmDomain is:
- /passwd <-- a DirDBM directory
- /USER/{cur, new, del} <-- each user has these three directories
- @ivar postmaster: See L{__init__}.
- @type dbm: L{DirDBM <dirdbm.DirDBM>}
- @ivar dbm: The authentication database for the domain.
- """
- portal = None
- _credcheckers = None
- def __init__(self, service, root, postmaster=0):
- """
- @type service: L{MailService}
- @param service: An email service.
- @type root: L{bytes}
- @param root: The maildir root directory.
- @type postmaster: L{bool}
- @param postmaster: A flag indicating whether non-existent addresses
- should be forwarded to the postmaster (C{True}) or
- bounced (C{False}).
- """
- AbstractMaildirDomain.__init__(self, service, root)
- dbm = os.path.join(root, 'passwd')
- if not os.path.exists(dbm):
- os.makedirs(dbm)
- self.dbm = dirdbm.open(dbm)
- self.postmaster = postmaster
- def userDirectory(self, name):
- """
- Return the path to a user's mail directory.
- @type name: L{bytes}
- @param name: A username.
- @rtype: L{bytes} or L{None}
- @return: The path to the user's mail directory for a valid user. For
- an invalid user, the path to the postmaster's mailbox if bounces
- are redirected there. Otherwise, L{None}.
- """
- if name not in self.dbm:
- if not self.postmaster:
- return None
- name = 'postmaster'
- dir = os.path.join(self.root, name)
- if not os.path.exists(dir):
- initializeMaildir(dir)
- return dir
- def addUser(self, user, password):
- """
- Add a user to this domain by adding an entry in the authentication
- database and initializing the user's mail directory.
- @type user: L{bytes}
- @param user: A username.
- @type password: L{bytes}
- @param password: A password.
- """
- self.dbm[user] = password
- # Ensure it is initialized
- self.userDirectory(user)
- def getCredentialsCheckers(self):
- """
- Return credentials checkers for this domain.
- @rtype: L{list} of L{ICredentialsChecker
- <checkers.ICredentialsChecker>} provider
- @return: Credentials checkers for this domain.
- """
- if self._credcheckers is None:
- self._credcheckers = [DirdbmDatabase(self.dbm)]
- return self._credcheckers
- def requestAvatar(self, avatarId, mind, *interfaces):
- """
- Get the mailbox for an authenticated user.
- The mailbox for the authenticated user will be returned only if the
- given interfaces include L{IMailbox <pop3.IMailbox>}. Requests for
- anonymous access will be met with a mailbox containing a message
- indicating that an internal error has occurred.
- @type avatarId: L{bytes} or C{twisted.cred.checkers.ANONYMOUS}
- @param avatarId: A string which identifies a user or an object which
- signals a request for anonymous access.
- @type mind: L{None}
- @param mind: Unused.
- @type interfaces: n-L{tuple} of C{zope.interface.Interface}
- @param interfaces: A group of interfaces, one of which the avatar
- must support.
- @rtype: 3-L{tuple} of (0) L{IMailbox <pop3.IMailbox>},
- (1) L{IMailbox <pop3.IMailbox>} provider, (2) no-argument
- callable
- @return: A tuple of the supported interface, a mailbox, and a
- logout function.
- @raise NotImplementedError: When the given interfaces do not include
- L{IMailbox <pop3.IMailbox>}.
- """
- if pop3.IMailbox not in interfaces:
- raise NotImplementedError("No interface")
- if avatarId == checkers.ANONYMOUS:
- mbox = StringListMailbox([INTERNAL_ERROR])
- else:
- mbox = MaildirMailbox(os.path.join(self.root, avatarId))
- return (
- pop3.IMailbox,
- mbox,
- lambda: None
- )
- @implementer(checkers.ICredentialsChecker)
- class DirdbmDatabase:
- """
- A credentials checker which authenticates users out of a
- L{DirDBM <dirdbm.DirDBM>} database.
- @type dirdbm: L{DirDBM <dirdbm.DirDBM>}
- @ivar dirdbm: An authentication database.
- """
- # credentialInterfaces is not used by the class
- credentialInterfaces = (
- credentials.IUsernamePassword,
- credentials.IUsernameHashedPassword
- )
- def __init__(self, dbm):
- """
- @type dbm: L{DirDBM <dirdbm.DirDBM>}
- @param dbm: An authentication database.
- """
- self.dirdbm = dbm
- def requestAvatarId(self, c):
- """
- Authenticate a user and, if successful, return their username.
- @type c: L{IUsernamePassword <credentials.IUsernamePassword>} or
- L{IUsernameHashedPassword <credentials.IUsernameHashedPassword>}
- provider.
- @param c: Credentials.
- @rtype: L{bytes}
- @return: A string which identifies an user.
- @raise UnauthorizedLogin: When the credentials check fails.
- """
- if c.username in self.dirdbm:
- if c.checkPassword(self.dirdbm[c.username]):
- return c.username
- raise UnauthorizedLogin()
|