17.12
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,404 @@
|
||||
# -*- test-case-name: twisted.mail.test.test_mail -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Mail protocol support.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
from twisted.mail import pop3
|
||||
from twisted.mail import smtp
|
||||
from twisted.internet import protocol
|
||||
from twisted.internet import defer
|
||||
from twisted.copyright import longversion
|
||||
from twisted.python import log
|
||||
|
||||
from twisted.cred.credentials import CramMD5Credentials, UsernamePassword
|
||||
from twisted.cred.error import UnauthorizedLogin
|
||||
|
||||
from twisted.mail import relay
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
|
||||
|
||||
@implementer(smtp.IMessageDelivery)
|
||||
class DomainDeliveryBase:
|
||||
"""
|
||||
A base class for message delivery using the domains of a mail service.
|
||||
|
||||
@ivar service: See L{__init__}
|
||||
@ivar user: See L{__init__}
|
||||
@ivar host: See L{__init__}
|
||||
|
||||
@type protocolName: L{bytes}
|
||||
@ivar protocolName: The protocol being used to deliver the mail.
|
||||
Sub-classes should set this appropriately.
|
||||
"""
|
||||
service = None
|
||||
protocolName = None
|
||||
|
||||
def __init__(self, service, user, host=smtp.DNSNAME):
|
||||
"""
|
||||
@type service: L{MailService}
|
||||
@param service: A mail service.
|
||||
|
||||
@type user: L{bytes} or L{None}
|
||||
@param user: The authenticated SMTP user.
|
||||
|
||||
@type host: L{bytes}
|
||||
@param host: The hostname.
|
||||
"""
|
||||
self.service = service
|
||||
self.user = user
|
||||
self.host = host
|
||||
|
||||
|
||||
def receivedHeader(self, helo, origin, recipients):
|
||||
"""
|
||||
Generate a received header string for a message.
|
||||
|
||||
@type helo: 2-L{tuple} of (L{bytes}, L{bytes})
|
||||
@param helo: The client's identity as sent in the HELO command and its
|
||||
IP address.
|
||||
|
||||
@type origin: L{Address}
|
||||
@param origin: The origination address of the message.
|
||||
|
||||
@type recipients: L{list} of L{User}
|
||||
@param recipients: The destination addresses for the message.
|
||||
|
||||
@rtype: L{bytes}
|
||||
@return: A received header string.
|
||||
"""
|
||||
authStr = heloStr = b""
|
||||
if self.user:
|
||||
authStr = b" auth=" + self.user.encode('xtext')
|
||||
if helo[0]:
|
||||
heloStr = b" helo=" + helo[0]
|
||||
fromUser = (b"from " + helo[0] + b" ([" + helo[1] + b"]" +
|
||||
heloStr + authStr)
|
||||
by = (b"by " + self.host + b" with " + self.protocolName +
|
||||
b" (" + longversion.encode("ascii") + b")")
|
||||
forUser = (b"for <" + b' '.join(map(bytes, recipients)) + b"> " +
|
||||
smtp.rfc822date())
|
||||
return (b"Received: " + fromUser + b"\n\t" + by +
|
||||
b"\n\t" + forUser)
|
||||
|
||||
|
||||
def validateTo(self, user):
|
||||
"""
|
||||
Validate the address for which a message is destined.
|
||||
|
||||
@type user: L{User}
|
||||
@param user: The destination address.
|
||||
|
||||
@rtype: L{Deferred <defer.Deferred>} which successfully fires with
|
||||
no-argument callable which returns L{IMessage <smtp.IMessage>}
|
||||
provider.
|
||||
@return: A deferred which successfully fires with a no-argument
|
||||
callable which returns a message receiver for the destination.
|
||||
|
||||
@raise SMTPBadRcpt: When messages cannot be accepted for the
|
||||
destination address.
|
||||
"""
|
||||
# XXX - Yick. This needs cleaning up.
|
||||
if self.user and self.service.queue:
|
||||
d = self.service.domains.get(user.dest.domain, None)
|
||||
if d is None:
|
||||
d = relay.DomainQueuer(self.service, True)
|
||||
else:
|
||||
d = self.service.domains[user.dest.domain]
|
||||
return defer.maybeDeferred(d.exists, user)
|
||||
|
||||
|
||||
def validateFrom(self, helo, origin):
|
||||
"""
|
||||
Validate the address from which a message originates.
|
||||
|
||||
@type helo: 2-L{tuple} of (L{bytes}, L{bytes})
|
||||
@param helo: The client's identity as sent in the HELO command and its
|
||||
IP address.
|
||||
|
||||
@type origin: L{Address}
|
||||
@param origin: The origination address of the message.
|
||||
|
||||
@rtype: L{Address}
|
||||
@return: The origination address.
|
||||
|
||||
@raise SMTPBadSender: When messages cannot be accepted from the
|
||||
origination address.
|
||||
"""
|
||||
if not helo:
|
||||
raise smtp.SMTPBadSender(origin, 503,
|
||||
"Who are you? Say HELO first.")
|
||||
if origin.local != b'' and origin.domain == b'':
|
||||
raise smtp.SMTPBadSender(origin, 501,
|
||||
"Sender address must contain domain.")
|
||||
return origin
|
||||
|
||||
|
||||
|
||||
class SMTPDomainDelivery(DomainDeliveryBase):
|
||||
"""
|
||||
A domain delivery base class for use in an SMTP server.
|
||||
"""
|
||||
protocolName = b'smtp'
|
||||
|
||||
|
||||
|
||||
class ESMTPDomainDelivery(DomainDeliveryBase):
|
||||
"""
|
||||
A domain delivery base class for use in an ESMTP server.
|
||||
"""
|
||||
protocolName = b'esmtp'
|
||||
|
||||
|
||||
|
||||
class SMTPFactory(smtp.SMTPFactory):
|
||||
"""
|
||||
An SMTP server protocol factory.
|
||||
|
||||
@ivar service: See L{__init__}
|
||||
@ivar portal: See L{__init__}
|
||||
|
||||
@type protocol: no-argument callable which returns a L{Protocol
|
||||
<protocol.Protocol>} subclass
|
||||
@ivar protocol: A callable which creates a protocol. The default value is
|
||||
L{SMTP}.
|
||||
"""
|
||||
protocol = smtp.SMTP
|
||||
portal = None
|
||||
|
||||
def __init__(self, service, portal = None):
|
||||
"""
|
||||
@type service: L{MailService}
|
||||
@param service: An email service.
|
||||
|
||||
@type portal: L{Portal <twisted.cred.portal.Portal>} or
|
||||
L{None}
|
||||
@param portal: A portal to use for authentication.
|
||||
"""
|
||||
smtp.SMTPFactory.__init__(self)
|
||||
self.service = service
|
||||
self.portal = portal
|
||||
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
"""
|
||||
Create an instance of an SMTP server protocol.
|
||||
|
||||
@type addr: L{IAddress <twisted.internet.interfaces.IAddress>} provider
|
||||
@param addr: The address of the SMTP client.
|
||||
|
||||
@rtype: L{SMTP}
|
||||
@return: An SMTP protocol.
|
||||
"""
|
||||
log.msg('Connection from %s' % (addr,))
|
||||
p = smtp.SMTPFactory.buildProtocol(self, addr)
|
||||
p.service = self.service
|
||||
p.portal = self.portal
|
||||
return p
|
||||
|
||||
|
||||
|
||||
class ESMTPFactory(SMTPFactory):
|
||||
"""
|
||||
An ESMTP server protocol factory.
|
||||
|
||||
@type protocol: no-argument callable which returns a L{Protocol
|
||||
<protocol.Protocol>} subclass
|
||||
@ivar protocol: A callable which creates a protocol. The default value is
|
||||
L{ESMTP}.
|
||||
|
||||
@type context: L{IOpenSSLContextFactory
|
||||
<twisted.internet.interfaces.IOpenSSLContextFactory>} or L{None}
|
||||
@ivar context: A factory to generate contexts to be used in negotiating
|
||||
encrypted communication.
|
||||
|
||||
@type challengers: L{dict} mapping L{bytes} to no-argument callable which
|
||||
returns L{ICredentials <twisted.cred.credentials.ICredentials>}
|
||||
subclass provider.
|
||||
@ivar challengers: A mapping of acceptable authorization mechanism to
|
||||
callable which creates credentials to use for authentication.
|
||||
"""
|
||||
protocol = smtp.ESMTP
|
||||
context = None
|
||||
|
||||
def __init__(self, *args):
|
||||
"""
|
||||
@param args: Arguments for L{SMTPFactory.__init__}
|
||||
|
||||
@see: L{SMTPFactory.__init__}
|
||||
"""
|
||||
SMTPFactory.__init__(self, *args)
|
||||
self.challengers = {
|
||||
b'CRAM-MD5': CramMD5Credentials
|
||||
}
|
||||
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
"""
|
||||
Create an instance of an ESMTP server protocol.
|
||||
|
||||
@type addr: L{IAddress <twisted.internet.interfaces.IAddress>} provider
|
||||
@param addr: The address of the ESMTP client.
|
||||
|
||||
@rtype: L{ESMTP}
|
||||
@return: An ESMTP protocol.
|
||||
"""
|
||||
p = SMTPFactory.buildProtocol(self, addr)
|
||||
p.challengers = self.challengers
|
||||
p.ctx = self.context
|
||||
return p
|
||||
|
||||
|
||||
|
||||
class VirtualPOP3(pop3.POP3):
|
||||
"""
|
||||
A virtual hosting POP3 server.
|
||||
|
||||
@type service: L{MailService}
|
||||
@ivar service: The email service that created this server. This must be
|
||||
set by the service.
|
||||
|
||||
@type domainSpecifier: L{bytes}
|
||||
@ivar domainSpecifier: The character to use to split an email address into
|
||||
local-part and domain. The default is '@'.
|
||||
"""
|
||||
service = None
|
||||
|
||||
domainSpecifier = b'@' # Gaagh! I hate POP3. No standardized way
|
||||
# to indicate user@host. '@' doesn't work
|
||||
# with NS, e.g.
|
||||
|
||||
def authenticateUserAPOP(self, user, digest):
|
||||
"""
|
||||
Perform APOP authentication.
|
||||
|
||||
Override the default lookup scheme to allow virtual domains.
|
||||
|
||||
@type user: L{bytes}
|
||||
@param user: The name of the user attempting to log in.
|
||||
|
||||
@type digest: L{bytes}
|
||||
@param digest: The challenge response.
|
||||
|
||||
@rtype: L{Deferred} which successfully results in 3-L{tuple} of
|
||||
(L{IMailbox <pop3.IMailbox>}, L{IMailbox <pop3.IMailbox>}
|
||||
provider, no-argument callable)
|
||||
@return: A deferred which fires when authentication is complete.
|
||||
If successful, it returns an L{IMailbox <pop3.IMailbox>} interface,
|
||||
a mailbox and a logout function. If authentication fails, the
|
||||
deferred fails with an L{UnauthorizedLogin
|
||||
<twisted.cred.error.UnauthorizedLogin>} error.
|
||||
"""
|
||||
user, domain = self.lookupDomain(user)
|
||||
try:
|
||||
portal = self.service.lookupPortal(domain)
|
||||
except KeyError:
|
||||
return defer.fail(UnauthorizedLogin())
|
||||
else:
|
||||
return portal.login(
|
||||
pop3.APOPCredentials(self.magic, user, digest),
|
||||
None,
|
||||
pop3.IMailbox
|
||||
)
|
||||
|
||||
|
||||
def authenticateUserPASS(self, user, password):
|
||||
"""
|
||||
Perform authentication for a username/password login.
|
||||
|
||||
Override the default lookup scheme to allow virtual domains.
|
||||
|
||||
@type user: L{bytes}
|
||||
@param user: The name of the user attempting to log in.
|
||||
|
||||
@type password: L{bytes}
|
||||
@param password: The password to authenticate with.
|
||||
|
||||
@rtype: L{Deferred} which successfully results in 3-L{tuple} of
|
||||
(L{IMailbox <pop3.IMailbox>}, L{IMailbox <pop3.IMailbox>}
|
||||
provider, no-argument callable)
|
||||
@return: A deferred which fires when authentication is complete.
|
||||
If successful, it returns an L{IMailbox <pop3.IMailbox>} interface,
|
||||
a mailbox and a logout function. If authentication fails, the
|
||||
deferred fails with an L{UnauthorizedLogin
|
||||
<twisted.cred.error.UnauthorizedLogin>} error.
|
||||
"""
|
||||
user, domain = self.lookupDomain(user)
|
||||
try:
|
||||
portal = self.service.lookupPortal(domain)
|
||||
except KeyError:
|
||||
return defer.fail(UnauthorizedLogin())
|
||||
else:
|
||||
return portal.login(
|
||||
UsernamePassword(user, password),
|
||||
None,
|
||||
pop3.IMailbox
|
||||
)
|
||||
|
||||
|
||||
def lookupDomain(self, user):
|
||||
"""
|
||||
Check whether a domain is among the virtual domains supported by the
|
||||
mail service.
|
||||
|
||||
@type user: L{bytes}
|
||||
@param user: An email address.
|
||||
|
||||
@rtype: 2-L{tuple} of (L{bytes}, L{bytes})
|
||||
@return: The local part and the domain part of the email address if the
|
||||
domain is supported.
|
||||
|
||||
@raise POP3Error: When the domain is not supported by the mail service.
|
||||
"""
|
||||
try:
|
||||
user, domain = user.split(self.domainSpecifier, 1)
|
||||
except ValueError:
|
||||
domain = b''
|
||||
if domain not in self.service.domains:
|
||||
raise pop3.POP3Error(
|
||||
"no such domain {}".format(domain.decode("utf-8")))
|
||||
return user, domain
|
||||
|
||||
|
||||
|
||||
class POP3Factory(protocol.ServerFactory):
|
||||
"""
|
||||
A POP3 server protocol factory.
|
||||
|
||||
@ivar service: See L{__init__}
|
||||
|
||||
@type protocol: no-argument callable which returns a L{Protocol
|
||||
<protocol.Protocol>} subclass
|
||||
@ivar protocol: A callable which creates a protocol. The default value is
|
||||
L{VirtualPOP3}.
|
||||
"""
|
||||
protocol = VirtualPOP3
|
||||
service = None
|
||||
|
||||
def __init__(self, service):
|
||||
"""
|
||||
@type service: L{MailService}
|
||||
@param service: An email service.
|
||||
"""
|
||||
self.service = service
|
||||
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
"""
|
||||
Create an instance of a POP3 server protocol.
|
||||
|
||||
@type addr: L{IAddress <twisted.internet.interfaces.IAddress>} provider
|
||||
@param addr: The address of the POP3 client.
|
||||
|
||||
@rtype: L{POP3}
|
||||
@return: A POP3 protocol.
|
||||
"""
|
||||
p = protocol.ServerFactory.buildProtocol(self, addr)
|
||||
p.service = self.service
|
||||
return p
|
||||
@@ -0,0 +1,402 @@
|
||||
# -*- test-case-name: twisted.mail.test.test_mailmail -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Implementation module for the I{mailmail} command.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import email.utils
|
||||
import os
|
||||
import sys
|
||||
import getpass
|
||||
try:
|
||||
# Python 3
|
||||
from configparser import ConfigParser
|
||||
except ImportError:
|
||||
# Python 2
|
||||
from ConfigParser import ConfigParser
|
||||
|
||||
from twisted.copyright import version
|
||||
from twisted.internet import reactor
|
||||
from twisted.logger import Logger, textFileLogObserver
|
||||
from twisted.mail import smtp
|
||||
from twisted.python.compat import NativeStringIO
|
||||
|
||||
GLOBAL_CFG = "/etc/mailmail"
|
||||
LOCAL_CFG = os.path.expanduser("~/.twisted/mailmail")
|
||||
SMARTHOST = '127.0.0.1'
|
||||
|
||||
ERROR_FMT = """\
|
||||
Subject: Failed Message Delivery
|
||||
|
||||
Message delivery failed. The following occurred:
|
||||
|
||||
%s
|
||||
--
|
||||
The Twisted sendmail application.
|
||||
"""
|
||||
|
||||
_logObserver = textFileLogObserver(sys.stderr)
|
||||
_log = Logger(observer=_logObserver)
|
||||
|
||||
|
||||
|
||||
class Options:
|
||||
"""
|
||||
Store the values of the parsed command-line options to the I{mailmail}
|
||||
script.
|
||||
|
||||
@type to: L{list} of L{str}
|
||||
@ivar to: The addresses to which to deliver this message.
|
||||
|
||||
@type sender: L{str}
|
||||
@ivar sender: The address from which this message is being sent.
|
||||
|
||||
@type body: C{file}
|
||||
@ivar body: The object from which the message is to be read.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
def getlogin():
|
||||
try:
|
||||
return os.getlogin()
|
||||
except:
|
||||
return getpass.getuser()
|
||||
|
||||
|
||||
_unsupportedOption = SystemExit("Unsupported option.")
|
||||
|
||||
|
||||
|
||||
def parseOptions(argv):
|
||||
o = Options()
|
||||
o.to = [e for e in argv if not e.startswith('-')]
|
||||
o.sender = getlogin()
|
||||
|
||||
# Just be very stupid
|
||||
|
||||
# Skip -bm -- it is the default
|
||||
|
||||
# Add a non-standard option for querying the version of this tool.
|
||||
if '--version' in argv:
|
||||
print('mailmail version:', version)
|
||||
raise SystemExit()
|
||||
|
||||
# -bp lists queue information. Screw that.
|
||||
if '-bp' in argv:
|
||||
raise _unsupportedOption
|
||||
|
||||
# -bs makes sendmail use stdin/stdout as its transport. Screw that.
|
||||
if '-bs' in argv:
|
||||
raise _unsupportedOption
|
||||
|
||||
# -F sets who the mail is from, but is overridable by the From header
|
||||
if '-F' in argv:
|
||||
o.sender = argv[argv.index('-F') + 1]
|
||||
o.to.remove(o.sender)
|
||||
|
||||
# -i and -oi makes us ignore lone "."
|
||||
if ('-i' in argv) or ('-oi' in argv):
|
||||
raise _unsupportedOption
|
||||
|
||||
# -odb is background delivery
|
||||
if '-odb' in argv:
|
||||
o.background = True
|
||||
else:
|
||||
o.background = False
|
||||
|
||||
# -odf is foreground delivery
|
||||
if '-odf' in argv:
|
||||
o.background = False
|
||||
else:
|
||||
o.background = True
|
||||
|
||||
# -oem and -em cause errors to be mailed back to the sender.
|
||||
# It is also the default.
|
||||
|
||||
# -oep and -ep cause errors to be printed to stderr
|
||||
if ('-oep' in argv) or ('-ep' in argv):
|
||||
o.printErrors = True
|
||||
else:
|
||||
o.printErrors = False
|
||||
|
||||
# -om causes a copy of the message to be sent to the sender if the sender
|
||||
# appears in an alias expansion. We do not support aliases.
|
||||
if '-om' in argv:
|
||||
raise _unsupportedOption
|
||||
|
||||
# -t causes us to pick the recipients of the message from
|
||||
# the To, Cc, and Bcc headers, and to remove the Bcc header
|
||||
# if present.
|
||||
if '-t' in argv:
|
||||
o.recipientsFromHeaders = True
|
||||
o.excludeAddresses = o.to
|
||||
o.to = []
|
||||
else:
|
||||
o.recipientsFromHeaders = False
|
||||
o.exludeAddresses = []
|
||||
|
||||
requiredHeaders = {
|
||||
'from': [],
|
||||
'to': [],
|
||||
'cc': [],
|
||||
'bcc': [],
|
||||
'date': [],
|
||||
}
|
||||
|
||||
buffer = NativeStringIO()
|
||||
while 1:
|
||||
write = 1
|
||||
line = sys.stdin.readline()
|
||||
if not line.strip():
|
||||
break
|
||||
|
||||
hdrs = line.split(': ', 1)
|
||||
|
||||
hdr = hdrs[0].lower()
|
||||
if o.recipientsFromHeaders and hdr in ('to', 'cc', 'bcc'):
|
||||
o.to.extend([
|
||||
email.utils.parseaddr(hdrs[1])[1]
|
||||
])
|
||||
if hdr == 'bcc':
|
||||
write = 0
|
||||
elif hdr == 'from':
|
||||
o.sender = email.utils.parseaddr(hdrs[1])[1]
|
||||
|
||||
if hdr in requiredHeaders:
|
||||
requiredHeaders[hdr].append(hdrs[1])
|
||||
|
||||
if write:
|
||||
buffer.write(line)
|
||||
|
||||
if not requiredHeaders['from']:
|
||||
buffer.write('From: {}\r\n'.format(o.sender))
|
||||
if not requiredHeaders['to']:
|
||||
if not o.to:
|
||||
raise SystemExit("No recipients specified.")
|
||||
buffer.write('To: {}\r\n'.format(', '.join(o.to)))
|
||||
if not requiredHeaders['date']:
|
||||
buffer.write('Date: {}\r\n'.format(smtp.rfc822date()))
|
||||
|
||||
buffer.write(line)
|
||||
|
||||
if o.recipientsFromHeaders:
|
||||
for a in o.excludeAddresses:
|
||||
try:
|
||||
o.to.remove(a)
|
||||
except:
|
||||
pass
|
||||
|
||||
buffer.seek(0, 0)
|
||||
o.body = NativeStringIO(buffer.getvalue() + sys.stdin.read())
|
||||
return o
|
||||
|
||||
|
||||
|
||||
class Configuration:
|
||||
"""
|
||||
|
||||
@ivar allowUIDs: A list of UIDs which are allowed to send mail.
|
||||
@ivar allowGIDs: A list of GIDs which are allowed to send mail.
|
||||
@ivar denyUIDs: A list of UIDs which are not allowed to send mail.
|
||||
@ivar denyGIDs: A list of GIDs which are not allowed to send mail.
|
||||
|
||||
@type defaultAccess: L{bool}
|
||||
@ivar defaultAccess: L{True} if access will be allowed when no other access
|
||||
control rule matches or L{False} if it will be denied in that case.
|
||||
|
||||
@ivar useraccess: Either C{'allow'} to check C{allowUID} first
|
||||
or C{'deny'} to check C{denyUID} first.
|
||||
|
||||
@ivar groupaccess: Either C{'allow'} to check C{allowGID} first or
|
||||
C{'deny'} to check C{denyGID} first.
|
||||
|
||||
@ivar identities: A L{dict} mapping hostnames to credentials to use when
|
||||
sending mail to that host.
|
||||
|
||||
@ivar smarthost: L{None} or a hostname through which all outgoing mail will
|
||||
be sent.
|
||||
|
||||
@ivar domain: L{None} or the hostname with which to identify ourselves when
|
||||
connecting to an MTA.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.allowUIDs = []
|
||||
self.denyUIDs = []
|
||||
self.allowGIDs = []
|
||||
self.denyGIDs = []
|
||||
self.useraccess = 'deny'
|
||||
self.groupaccess = 'deny'
|
||||
|
||||
self.identities = {}
|
||||
self.smarthost = None
|
||||
self.domain = None
|
||||
|
||||
self.defaultAccess = True
|
||||
|
||||
|
||||
|
||||
def loadConfig(path):
|
||||
# [useraccess]
|
||||
# allow=uid1,uid2,...
|
||||
# deny=uid1,uid2,...
|
||||
# order=allow,deny
|
||||
# [groupaccess]
|
||||
# allow=gid1,gid2,...
|
||||
# deny=gid1,gid2,...
|
||||
# order=deny,allow
|
||||
# [identity]
|
||||
# host1=username:password
|
||||
# host2=username:password
|
||||
# [addresses]
|
||||
# smarthost=a.b.c.d
|
||||
# default_domain=x.y.z
|
||||
|
||||
c = Configuration()
|
||||
|
||||
if not os.access(path, os.R_OK):
|
||||
return c
|
||||
|
||||
p = ConfigParser()
|
||||
p.read(path)
|
||||
|
||||
au = c.allowUIDs
|
||||
du = c.denyUIDs
|
||||
ag = c.allowGIDs
|
||||
dg = c.denyGIDs
|
||||
for (section, a, d) in (('useraccess', au, du), ('groupaccess', ag, dg)):
|
||||
if p.has_section(section):
|
||||
for (mode, L) in (('allow', a), ('deny', d)):
|
||||
if p.has_option(section, mode) and p.get(section, mode):
|
||||
for sectionID in p.get(section, mode).split(','):
|
||||
try:
|
||||
sectionID = int(sectionID)
|
||||
except ValueError:
|
||||
_log.error(
|
||||
"Illegal {prefix}ID in "
|
||||
"[{section}] section: {sectionID}",
|
||||
prefix=section[0].upper(),
|
||||
section=section, sectionID=sectionID)
|
||||
else:
|
||||
L.append(sectionID)
|
||||
order = p.get(section, 'order')
|
||||
order = [s.split()
|
||||
for s in [s.lower()
|
||||
for s in order.split(',')]]
|
||||
if order[0] == 'allow':
|
||||
setattr(c, section, 'allow')
|
||||
else:
|
||||
setattr(c, section, 'deny')
|
||||
|
||||
if p.has_section('identity'):
|
||||
for (host, up) in p.items('identity'):
|
||||
parts = up.split(':', 1)
|
||||
if len(parts) != 2:
|
||||
_log.error("Illegal entry in [identity] section: {section}",
|
||||
section=up)
|
||||
continue
|
||||
c.identities[host] = parts
|
||||
|
||||
if p.has_section('addresses'):
|
||||
if p.has_option('addresses', 'smarthost'):
|
||||
c.smarthost = p.get('addresses', 'smarthost')
|
||||
if p.has_option('addresses', 'default_domain'):
|
||||
c.domain = p.get('addresses', 'default_domain')
|
||||
|
||||
return c
|
||||
|
||||
|
||||
|
||||
def success(result):
|
||||
reactor.stop()
|
||||
|
||||
|
||||
|
||||
failed = None
|
||||
def failure(f):
|
||||
global failed
|
||||
reactor.stop()
|
||||
failed = f
|
||||
|
||||
|
||||
|
||||
def sendmail(host, options, ident):
|
||||
d = smtp.sendmail(host, options.sender, options.to, options.body)
|
||||
d.addCallbacks(success, failure)
|
||||
reactor.run()
|
||||
|
||||
|
||||
|
||||
def senderror(failure, options):
|
||||
recipient = [options.sender]
|
||||
sender = '"Internally Generated Message ({})"<postmaster@{}>'.format(
|
||||
sys.argv[0], smtp.DNSNAME.decode("ascii"))
|
||||
error = NativeStringIO()
|
||||
failure.printTraceback(file=error)
|
||||
body = NativeStringIO(ERROR_FMT % error.getvalue())
|
||||
d = smtp.sendmail('localhost', sender, recipient, body)
|
||||
d.addBoth(lambda _: reactor.stop())
|
||||
|
||||
|
||||
|
||||
def deny(conf):
|
||||
uid = os.getuid()
|
||||
gid = os.getgid()
|
||||
|
||||
if conf.useraccess == 'deny':
|
||||
if uid in conf.denyUIDs:
|
||||
return True
|
||||
if uid in conf.allowUIDs:
|
||||
return False
|
||||
else:
|
||||
if uid in conf.allowUIDs:
|
||||
return False
|
||||
if uid in conf.denyUIDs:
|
||||
return True
|
||||
|
||||
if conf.groupaccess == 'deny':
|
||||
if gid in conf.denyGIDs:
|
||||
return True
|
||||
if gid in conf.allowGIDs:
|
||||
return False
|
||||
else:
|
||||
if gid in conf.allowGIDs:
|
||||
return False
|
||||
if gid in conf.denyGIDs:
|
||||
return True
|
||||
|
||||
return not conf.defaultAccess
|
||||
|
||||
|
||||
|
||||
def run():
|
||||
o = parseOptions(sys.argv[1:])
|
||||
gConf = loadConfig(GLOBAL_CFG)
|
||||
lConf = loadConfig(LOCAL_CFG)
|
||||
|
||||
if deny(gConf) or deny(lConf):
|
||||
_log.error("Permission denied")
|
||||
return
|
||||
|
||||
host = lConf.smarthost or gConf.smarthost or SMARTHOST
|
||||
|
||||
ident = gConf.identities.copy()
|
||||
ident.update(lConf.identities)
|
||||
|
||||
if lConf.domain:
|
||||
smtp.DNSNAME = lConf.domain
|
||||
elif gConf.domain:
|
||||
smtp.DNSNAME = gConf.domain
|
||||
|
||||
sendmail(host, o, ident)
|
||||
|
||||
if failed:
|
||||
if o.printErrors:
|
||||
failed.printTraceback(file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
else:
|
||||
senderror(failed, o)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user