aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py2/twisted/mail/scripts/mailmail.py
diff options
context:
space:
mode:
authormaxim-yurchuk <maxim-yurchuk@yandex-team.com>2024-10-09 12:29:46 +0300
committermaxim-yurchuk <maxim-yurchuk@yandex-team.com>2024-10-09 13:14:22 +0300
commit9731d8a4bb7ee2cc8554eaf133bb85498a4c7d80 (patch)
treea8fb3181d5947c0d78cf402aa56e686130179049 /contrib/python/Twisted/py2/twisted/mail/scripts/mailmail.py
parenta44b779cd359f06c3ebbef4ec98c6b38609d9d85 (diff)
downloadydb-9731d8a4bb7ee2cc8554eaf133bb85498a4c7d80.tar.gz
publishFullContrib: true for ydb
<HIDDEN_URL> commit_hash:c82a80ac4594723cebf2c7387dec9c60217f603e
Diffstat (limited to 'contrib/python/Twisted/py2/twisted/mail/scripts/mailmail.py')
-rw-r--r--contrib/python/Twisted/py2/twisted/mail/scripts/mailmail.py402
1 files changed, 402 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py2/twisted/mail/scripts/mailmail.py b/contrib/python/Twisted/py2/twisted/mail/scripts/mailmail.py
new file mode 100644
index 0000000000..2aa5d46066
--- /dev/null
+++ b/contrib/python/Twisted/py2/twisted/mail/scripts/mailmail.py
@@ -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)