aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py2/twisted/mail/alias.py
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py2/twisted/mail/alias.py
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py2/twisted/mail/alias.py')
-rw-r--r--contrib/python/Twisted/py2/twisted/mail/alias.py799
1 files changed, 799 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py2/twisted/mail/alias.py b/contrib/python/Twisted/py2/twisted/mail/alias.py
new file mode 100644
index 0000000000..32229bda7d
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/mail/alias.py
@@ -0,0 +1,799 @@
+# -*- test-case-name: twisted.mail.test.test_mail -*-
+#
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+
+"""
+Support for aliases(5) configuration files.
+
+@author: Jp Calderone
+"""
+
+import os
+import tempfile
+
+from twisted.mail import smtp
+from twisted.mail.interfaces import IAlias
+from twisted.internet import reactor
+from twisted.internet import protocol
+from twisted.internet import defer
+from twisted.python import failure
+from twisted.python import log
+from zope.interface import implementer
+
+
+def handle(result, line, filename, lineNo):
+ """
+ Parse a line from an aliases file.
+
+ @type result: L{dict} mapping L{bytes} to L{list} of L{bytes}
+ @param result: A dictionary mapping username to aliases to which
+ the results of parsing the line are added.
+
+ @type line: L{bytes}
+ @param line: A line from an aliases file.
+
+ @type filename: L{bytes}
+ @param filename: The full or relative path to the aliases file.
+
+ @type lineNo: L{int}
+ @param lineNo: The position of the line within the aliases file.
+ """
+ parts = [p.strip() for p in line.split(':', 1)]
+ if len(parts) != 2:
+ fmt = "Invalid format on line %d of alias file %s."
+ arg = (lineNo, filename)
+ log.err(fmt % arg)
+ else:
+ user, alias = parts
+ result.setdefault(user.strip(), []).extend(map(str.strip, alias.split(',')))
+
+
+
+def loadAliasFile(domains, filename=None, fp=None):
+ """
+ Load a file containing email aliases.
+
+ Lines in the file should be formatted like so::
+
+ username: alias1, alias2, ..., aliasN
+
+ Aliases beginning with a C{|} will be treated as programs, will be run, and
+ the message will be written to their stdin.
+
+ Aliases beginning with a C{:} will be treated as a file containing
+ additional aliases for the username.
+
+ Aliases beginning with a C{/} will be treated as the full pathname to a file
+ to which the message will be appended.
+
+ Aliases without a host part will be assumed to be addresses on localhost.
+
+ If a username is specified multiple times, the aliases for each are joined
+ together as if they had all been on one line.
+
+ Lines beginning with a space or a tab are continuations of the previous
+ line.
+
+ Lines beginning with a C{#} are comments.
+
+ @type domains: L{dict} mapping L{bytes} to L{IDomain} provider
+ @param domains: A mapping of domain name to domain object.
+
+ @type filename: L{bytes} or L{None}
+ @param filename: The full or relative path to a file from which to load
+ aliases. If omitted, the C{fp} parameter must be specified.
+
+ @type fp: file-like object or L{None}
+ @param fp: The file from which to load aliases. If specified,
+ the C{filename} parameter is ignored.
+
+ @rtype: L{dict} mapping L{bytes} to L{AliasGroup}
+ @return: A mapping from username to group of aliases.
+ """
+ result = {}
+ close = False
+ if fp is None:
+ fp = open(filename)
+ close = True
+ else:
+ filename = getattr(fp, 'name', '<unknown>')
+ i = 0
+ prev = ''
+ try:
+ for line in fp:
+ i += 1
+ line = line.rstrip()
+ if line.lstrip().startswith('#'):
+ continue
+ elif line.startswith(' ') or line.startswith('\t'):
+ prev = prev + line
+ else:
+ if prev:
+ handle(result, prev, filename, i)
+ prev = line
+ finally:
+ if close:
+ fp.close()
+ if prev:
+ handle(result, prev, filename, i)
+ for (u, a) in result.items():
+ result[u] = AliasGroup(a, domains, u)
+ return result
+
+
+
+class AliasBase:
+ """
+ The default base class for aliases.
+
+ @ivar domains: See L{__init__}.
+
+ @type original: L{Address}
+ @ivar original: The original address being aliased.
+ """
+ def __init__(self, domains, original):
+ """
+ @type domains: L{dict} mapping L{bytes} to L{IDomain} provider
+ @param domains: A mapping of domain name to domain object.
+
+ @type original: L{bytes}
+ @param original: The original address being aliased.
+ """
+ self.domains = domains
+ self.original = smtp.Address(original)
+
+
+ def domain(self):
+ """
+ Return the domain associated with original address.
+
+ @rtype: L{IDomain} provider
+ @return: The domain for the original address.
+ """
+ return self.domains[self.original.domain]
+
+
+ def resolve(self, aliasmap, memo=None):
+ """
+ Map this alias to its ultimate destination.
+
+ @type aliasmap: L{dict} mapping L{bytes} to L{AliasBase}
+ @param aliasmap: A mapping of username to alias or group of aliases.
+
+ @type memo: L{None} or L{dict} of L{AliasBase}
+ @param memo: A record of the aliases already considered in the
+ resolution process. If provided, C{memo} is modified to include
+ this alias.
+
+ @rtype: L{IMessage <smtp.IMessage>} or L{None}
+ @return: A message receiver for the ultimate destination or None for
+ an invalid destination.
+ """
+ if memo is None:
+ memo = {}
+ if str(self) in memo:
+ return None
+ memo[str(self)] = None
+ return self.createMessageReceiver()
+
+
+
+@implementer(IAlias)
+class AddressAlias(AliasBase):
+ """
+ An alias which translates one email address into another.
+
+ @type alias : L{Address}
+ @ivar alias: The destination address.
+ """
+ def __init__(self, alias, *args):
+ """
+ @type alias: L{Address}, L{User}, L{bytes} or object which can be
+ converted into L{bytes}
+ @param alias: The destination address.
+
+ @type args: 2-L{tuple} of (0) L{dict} mapping L{bytes} to L{IDomain}
+ provider, (1) L{bytes}
+ @param args: Arguments for L{AliasBase.__init__}.
+ """
+ AliasBase.__init__(self, *args)
+ self.alias = smtp.Address(alias)
+
+
+ def __str__(self):
+ """
+ Build a string representation of this L{AddressAlias} instance.
+
+ @rtype: L{bytes}
+ @return: A string containing the destination address.
+ """
+ return '<Address %s>' % (self.alias,)
+
+
+ def createMessageReceiver(self):
+ """
+ Create a message receiver which delivers a message to
+ the destination address.
+
+ @rtype: L{IMessage <smtp.IMessage>} provider
+ @return: A message receiver.
+ """
+ return self.domain().exists(str(self.alias))
+
+
+ def resolve(self, aliasmap, memo=None):
+ """
+ Map this alias to its ultimate destination.
+
+ @type aliasmap: L{dict} mapping L{bytes} to L{AliasBase}
+ @param aliasmap: A mapping of username to alias or group of aliases.
+
+ @type memo: L{None} or L{dict} of L{AliasBase}
+ @param memo: A record of the aliases already considered in the
+ resolution process. If provided, C{memo} is modified to include
+ this alias.
+
+ @rtype: L{IMessage <smtp.IMessage>} or L{None}
+ @return: A message receiver for the ultimate destination or None for
+ an invalid destination.
+ """
+ if memo is None:
+ memo = {}
+ if str(self) in memo:
+ return None
+ memo[str(self)] = None
+ try:
+ return self.domain().exists(smtp.User(self.alias, None, None, None), memo)()
+ except smtp.SMTPBadRcpt:
+ pass
+ if self.alias.local in aliasmap:
+ return aliasmap[self.alias.local].resolve(aliasmap, memo)
+ return None
+
+
+
+@implementer(smtp.IMessage)
+class FileWrapper:
+ """
+ A message receiver which delivers a message to a file.
+
+ @type fp: file-like object
+ @ivar fp: A file used for temporary storage of the message.
+
+ @type finalname: L{bytes}
+ @ivar finalname: The name of the file in which the message should be
+ stored.
+ """
+ def __init__(self, filename):
+ """
+ @type filename: L{bytes}
+ @param filename: The name of the file in which the message should be
+ stored.
+ """
+ self.fp = tempfile.TemporaryFile()
+ self.finalname = filename
+
+
+ def lineReceived(self, line):
+ """
+ Write a received line to the temporary file.
+
+ @type line: L{bytes}
+ @param line: A received line of the message.
+ """
+ self.fp.write(line + '\n')
+
+
+ def eomReceived(self):
+ """
+ Handle end of message by writing the message to the file.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully results in
+ L{bytes}
+ @return: A deferred which succeeds with the name of the file to which
+ the message has been stored or fails if the message cannot be
+ saved to the file.
+ """
+ self.fp.seek(0, 0)
+ try:
+ f = open(self.finalname, 'a')
+ except:
+ return defer.fail(failure.Failure())
+
+ with f:
+ f.write(self.fp.read())
+ self.fp.close()
+
+ return defer.succeed(self.finalname)
+
+
+ def connectionLost(self):
+ """
+ Close the temporary file when the connection is lost.
+ """
+ self.fp.close()
+ self.fp = None
+
+
+ def __str__(self):
+ """
+ Build a string representation of this L{FileWrapper} instance.
+
+ @rtype: L{bytes}
+ @return: A string containing the file name of the message.
+ """
+ return '<FileWrapper %s>' % (self.finalname,)
+
+
+
+@implementer(IAlias)
+class FileAlias(AliasBase):
+ """
+ An alias which translates an address to a file.
+
+ @ivar filename: See L{__init__}.
+ """
+ def __init__(self, filename, *args):
+ """
+ @type filename: L{bytes}
+ @param filename: The name of the file in which to store the message.
+
+ @type args: 2-L{tuple} of (0) L{dict} mapping L{bytes} to L{IDomain}
+ provider, (1) L{bytes}
+ @param args: Arguments for L{AliasBase.__init__}.
+ """
+ AliasBase.__init__(self, *args)
+ self.filename = filename
+
+
+ def __str__(self):
+ """
+ Build a string representation of this L{FileAlias} instance.
+
+ @rtype: L{bytes}
+ @return: A string containing the name of the file.
+ """
+ return '<File %s>' % (self.filename,)
+
+
+ def createMessageReceiver(self):
+ """
+ Create a message receiver which delivers a message to the file.
+
+ @rtype: L{FileWrapper}
+ @return: A message receiver which writes a message to the file.
+ """
+ return FileWrapper(self.filename)
+
+
+
+class ProcessAliasTimeout(Exception):
+ """
+ An error indicating that a timeout occurred while waiting for a process
+ to complete.
+ """
+
+
+
+@implementer(smtp.IMessage)
+class MessageWrapper:
+ """
+ A message receiver which delivers a message to a child process.
+
+ @type completionTimeout: L{int} or L{float}
+ @ivar completionTimeout: The number of seconds to wait for the child
+ process to exit before reporting the delivery as a failure.
+
+ @type _timeoutCallID: L{None} or
+ L{IDelayedCall <twisted.internet.interfaces.IDelayedCall>} provider
+ @ivar _timeoutCallID: The call used to time out delivery, started when the
+ connection to the child process is closed.
+
+ @type done: L{bool}
+ @ivar done: A flag indicating whether the child process has exited
+ (C{True}) or not (C{False}).
+
+ @type reactor: L{IReactorTime <twisted.internet.interfaces.IReactorTime>}
+ provider
+ @ivar reactor: A reactor which will be used to schedule timeouts.
+
+ @ivar protocol: See L{__init__}.
+
+ @type processName: L{bytes} or L{None}
+ @ivar processName: The process name.
+
+ @type completion: L{Deferred <defer.Deferred>}
+ @ivar completion: The deferred which will be triggered by the protocol
+ when the child process exits.
+ """
+ done = False
+
+ completionTimeout = 60
+ _timeoutCallID = None
+
+ reactor = reactor
+
+ def __init__(self, protocol, process=None, reactor=None):
+ """
+ @type protocol: L{ProcessAliasProtocol}
+ @param protocol: The protocol associated with the child process.
+
+ @type process: L{bytes} or L{None}
+ @param process: The process name.
+
+ @type reactor: L{None} or L{IReactorTime
+ <twisted.internet.interfaces.IReactorTime>} provider
+ @param reactor: A reactor which will be used to schedule timeouts.
+ """
+ self.processName = process
+ self.protocol = protocol
+ self.completion = defer.Deferred()
+ self.protocol.onEnd = self.completion
+ self.completion.addBoth(self._processEnded)
+
+ if reactor is not None:
+ self.reactor = reactor
+
+
+ def _processEnded(self, result):
+ """
+ Record process termination and cancel the timeout call if it is active.
+
+ @type result: L{Failure <failure.Failure>}
+ @param result: The reason the child process terminated.
+
+ @rtype: L{None} or L{Failure <failure.Failure>}
+ @return: None, if the process end is expected, or the reason the child
+ process terminated, if the process end is unexpected.
+ """
+ self.done = True
+ if self._timeoutCallID is not None:
+ # eomReceived was called, we're actually waiting for the process to
+ # exit.
+ self._timeoutCallID.cancel()
+ self._timeoutCallID = None
+ else:
+ # eomReceived was not called, this is unexpected, propagate the
+ # error.
+ return result
+
+
+ def lineReceived(self, line):
+ """
+ Write a received line to the child process.
+
+ @type line: L{bytes}
+ @param line: A received line of the message.
+ """
+ if self.done:
+ return
+ self.protocol.transport.write(line + '\n')
+
+
+ def eomReceived(self):
+ """
+ Disconnect from the child process and set up a timeout to wait for it
+ to exit.
+
+ @rtype: L{Deferred <defer.Deferred>}
+ @return: A deferred which will be called back when the child process
+ exits.
+ """
+ if not self.done:
+ self.protocol.transport.loseConnection()
+ self._timeoutCallID = self.reactor.callLater(
+ self.completionTimeout, self._completionCancel)
+ return self.completion
+
+
+ def _completionCancel(self):
+ """
+ Handle the expiration of the timeout for the child process to exit by
+ terminating the child process forcefully and issuing a failure to the
+ L{completion} deferred.
+ """
+ self._timeoutCallID = None
+ self.protocol.transport.signalProcess('KILL')
+ exc = ProcessAliasTimeout(
+ "No answer after %s seconds" % (self.completionTimeout,))
+ self.protocol.onEnd = None
+ self.completion.errback(failure.Failure(exc))
+
+
+ def connectionLost(self):
+ """
+ Ignore notification of lost connection.
+ """
+
+
+ def __str__(self):
+ """
+ Build a string representation of this L{MessageWrapper} instance.
+
+ @rtype: L{bytes}
+ @return: A string containing the name of the process.
+ """
+ return '<ProcessWrapper %s>' % (self.processName,)
+
+
+
+class ProcessAliasProtocol(protocol.ProcessProtocol):
+ """
+ A process protocol which errbacks a deferred when the associated
+ process ends.
+
+ @type onEnd: L{None} or L{Deferred <defer.Deferred>}
+ @ivar onEnd: If set, a deferred on which to errback when the process ends.
+ """
+ onEnd = None
+
+ def processEnded(self, reason):
+ """
+ Call an errback.
+
+ @type reason: L{Failure <failure.Failure>}
+ @param reason: The reason the child process terminated.
+ """
+ if self.onEnd is not None:
+ self.onEnd.errback(reason)
+
+
+
+@implementer(IAlias)
+class ProcessAlias(AliasBase):
+ """
+ An alias which is handled by the execution of a program.
+
+ @type path: L{list} of L{bytes}
+ @ivar path: The arguments to pass to the process. The first string is
+ the executable's name.
+
+ @type program: L{bytes}
+ @ivar program: The path of the program to be executed.
+
+ @type reactor: L{IReactorTime <twisted.internet.interfaces.IReactorTime>}
+ and L{IReactorProcess <twisted.internet.interfaces.IReactorProcess>}
+ provider
+ @ivar reactor: A reactor which will be used to create and timeout the
+ child process.
+ """
+ reactor = reactor
+
+ def __init__(self, path, *args):
+ """
+ @type path: L{bytes}
+ @param path: The command to invoke the program consisting of the path
+ to the executable followed by any arguments.
+
+ @type args: 2-L{tuple} of (0) L{dict} mapping L{bytes} to L{IDomain}
+ provider, (1) L{bytes}
+ @param args: Arguments for L{AliasBase.__init__}.
+ """
+
+ AliasBase.__init__(self, *args)
+ self.path = path.split()
+ self.program = self.path[0]
+
+
+ def __str__(self):
+ """
+ Build a string representation of this L{ProcessAlias} instance.
+
+ @rtype: L{bytes}
+ @return: A string containing the command used to invoke the process.
+ """
+ return '<Process %s>' % (self.path,)
+
+
+ def spawnProcess(self, proto, program, path):
+ """
+ Spawn a process.
+
+ This wraps the L{spawnProcess
+ <twisted.internet.interfaces.IReactorProcess.spawnProcess>} method on
+ L{reactor} so that it can be customized for test purposes.
+
+ @type proto: L{IProcessProtocol
+ <twisted.internet.interfaces.IProcessProtocol>} provider
+ @param proto: An object which will be notified of all events related to
+ the created process.
+
+ @type program: L{bytes}
+ @param program: The full path name of the file to execute.
+
+ @type path: L{list} of L{bytes}
+ @param path: The arguments to pass to the process. The first string
+ should be the executable's name.
+
+ @rtype: L{IProcessTransport
+ <twisted.internet.interfaces.IProcessTransport>} provider
+ @return: A process transport.
+ """
+ return self.reactor.spawnProcess(proto, program, path)
+
+
+ def createMessageReceiver(self):
+ """
+ Launch a process and create a message receiver to pass a message
+ to the process.
+
+ @rtype: L{MessageWrapper}
+ @return: A message receiver which delivers a message to the process.
+ """
+ p = ProcessAliasProtocol()
+ m = MessageWrapper(p, self.program, self.reactor)
+ self.spawnProcess(p, self.program, self.path)
+ return m
+
+
+
+@implementer(smtp.IMessage)
+class MultiWrapper:
+ """
+ A message receiver which delivers a single message to multiple other
+ message receivers.
+
+ @ivar objs: See L{__init__}.
+ """
+ def __init__(self, objs):
+ """
+ @type objs: L{list} of L{IMessage <smtp.IMessage>} provider
+ @param objs: Message receivers to which the incoming message should be
+ directed.
+ """
+ self.objs = objs
+
+
+ def lineReceived(self, line):
+ """
+ Pass a received line to the message receivers.
+
+ @type line: L{bytes}
+ @param line: A line of the message.
+ """
+ for o in self.objs:
+ o.lineReceived(line)
+
+
+ def eomReceived(self):
+ """
+ Pass the end of message along to the message receivers.
+
+ @rtype: L{DeferredList <defer.DeferredList>} whose successful results
+ are L{bytes} or L{None}
+ @return: A deferred list which triggers when all of the message
+ receivers have finished handling their end of message.
+ """
+ return defer.DeferredList([
+ o.eomReceived() for o in self.objs
+ ])
+
+
+ def connectionLost(self):
+ """
+ Inform the message receivers that the connection has been lost.
+ """
+ for o in self.objs:
+ o.connectionLost()
+
+
+ def __str__(self):
+ """
+ Build a string representation of this L{MultiWrapper} instance.
+
+ @rtype: L{bytes}
+ @return: A string containing a list of the message receivers.
+ """
+ return '<GroupWrapper %r>' % (map(str, self.objs),)
+
+
+
+@implementer(IAlias)
+class AliasGroup(AliasBase):
+ """
+ An alias which points to multiple destination aliases.
+
+ @type processAliasFactory: no-argument callable which returns
+ L{ProcessAlias}
+ @ivar processAliasFactory: A factory for process aliases.
+
+ @type aliases: L{list} of L{AliasBase} which implements L{IAlias}
+ @ivar aliases: The destination aliases.
+ """
+ processAliasFactory = ProcessAlias
+
+ def __init__(self, items, *args):
+ """
+ Create a group of aliases.
+
+ Parse a list of alias strings and, for each, create an appropriate
+ alias object.
+
+ @type items: L{list} of L{bytes}
+ @param items: Aliases.
+
+ @type args: n-L{tuple} of (0) L{dict} mapping L{bytes} to L{IDomain}
+ provider, (1) L{bytes}
+ @param args: Arguments for L{AliasBase.__init__}.
+ """
+
+ AliasBase.__init__(self, *args)
+ self.aliases = []
+ while items:
+ addr = items.pop().strip()
+ if addr.startswith(':'):
+ try:
+ f = open(addr[1:])
+ except:
+ log.err("Invalid filename in alias file %r" % (addr[1:],))
+ else:
+ with f:
+ addr = ' '.join([l.strip() for l in f])
+ items.extend(addr.split(','))
+ elif addr.startswith('|'):
+ self.aliases.append(self.processAliasFactory(addr[1:], *args))
+ elif addr.startswith('/'):
+ if os.path.isdir(addr):
+ log.err("Directory delivery not supported")
+ else:
+ self.aliases.append(FileAlias(addr, *args))
+ else:
+ self.aliases.append(AddressAlias(addr, *args))
+
+
+ def __len__(self):
+ """
+ Return the number of aliases in the group.
+
+ @rtype: L{int}
+ @return: The number of aliases in the group.
+ """
+ return len(self.aliases)
+
+
+ def __str__(self):
+ """
+ Build a string representation of this L{AliasGroup} instance.
+
+ @rtype: L{bytes}
+ @return: A string containing the aliases in the group.
+ """
+ return '<AliasGroup [%s]>' % (', '.join(map(str, self.aliases)))
+
+
+ def createMessageReceiver(self):
+ """
+ Create a message receiver for each alias and return a message receiver
+ which will pass on a message to each of those.
+
+ @rtype: L{MultiWrapper}
+ @return: A message receiver which passes a message on to message
+ receivers for each alias in the group.
+ """
+ return MultiWrapper([a.createMessageReceiver() for a in self.aliases])
+
+
+ def resolve(self, aliasmap, memo=None):
+ """
+ Map each of the aliases in the group to its ultimate destination.
+
+ @type aliasmap: L{dict} mapping L{bytes} to L{AliasBase}
+ @param aliasmap: A mapping of username to alias or group of aliases.
+
+ @type memo: L{None} or L{dict} of L{AliasBase}
+ @param memo: A record of the aliases already considered in the
+ resolution process. If provided, C{memo} is modified to include
+ this alias.
+
+ @rtype: L{MultiWrapper}
+ @return: A message receiver which passes the message on to message
+ receivers for the ultimate destination of each alias in the group.
+ """
+ if memo is None:
+ memo = {}
+ r = []
+ for a in self.aliases:
+ r.append(a.resolve(aliasmap, memo))
+ return MultiWrapper(filter(None, r))