1
# -*- test-case-name: twisted.mail.test.test_mailmail -*-
2
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
3
# See LICENSE for details.
6
Implementation module for the I{mailmail} command.
13
from ConfigParser import ConfigParser
16
import cStringIO as StringIO
20
from twisted.internet import reactor
21
from twisted.mail import smtp
23
GLOBAL_CFG = "/etc/mailmail"
24
LOCAL_CFG = os.path.expanduser("~/.twisted/mailmail")
25
SMARTHOST = '127.0.0.1'
28
Subject: Failed Message Delivery
30
Message delivery failed. The following occurred:
34
The Twisted sendmail application.
37
def log(message, *args):
38
sys.stderr.write(str(message) % args + '\n')
42
@type to: C{list} of C{str}
43
@ivar to: The addresses to which to deliver this message.
46
@ivar sender: The address from which this message is being sent.
49
@ivar body: The object from which the message is to be read.
56
return getpass.getuser()
59
_unsupportedOption = SystemExit("Unsupported option.")
61
def parseOptions(argv):
63
o.to = [e for e in argv if not e.startswith('-')]
68
# Skip -bm -- it is the default
70
# -bp lists queue information. Screw that.
72
raise _unsupportedOption
74
# -bs makes sendmail use stdin/stdout as its transport. Screw that.
76
raise _unsupportedOption
78
# -F sets who the mail is from, but is overridable by the From header
80
o.sender = argv[argv.index('-F') + 1]
83
# -i and -oi makes us ignore lone "."
84
if ('-i' in argv) or ('-oi' in argv):
85
raise _unsupportedOption
87
# -odb is background delivery
93
# -odf is foreground delivery
99
# -oem and -em cause errors to be mailed back to the sender.
100
# It is also the default.
102
# -oep and -ep cause errors to be printed to stderr
103
if ('-oep' in argv) or ('-ep' in argv):
106
o.printErrors = False
108
# -om causes a copy of the message to be sent to the sender if the sender
109
# appears in an alias expansion. We do not support aliases.
111
raise _unsupportedOption
113
# -t causes us to pick the recipients of the message from the To, Cc, and Bcc
114
# headers, and to remove the Bcc header if present.
116
o.recipientsFromHeaders = True
117
o.excludeAddresses = o.to
120
o.recipientsFromHeaders = False
121
o.exludeAddresses = []
132
buffer = StringIO.StringIO()
135
line = sys.stdin.readline()
139
hdrs = line.split(': ', 1)
141
hdr = hdrs[0].lower()
142
if o.recipientsFromHeaders and hdr in ('to', 'cc', 'bcc'):
144
a[1] for a in rfc822.AddressList(hdrs[1]).addresslist
149
o.sender = rfc822.parseaddr(hdrs[1])[1]
151
if hdr in requiredHeaders:
152
requiredHeaders[hdr].append(hdrs[1])
157
if not requiredHeaders['from']:
158
buffer.write('From: %s\r\n' % (o.sender,))
159
if not requiredHeaders['to']:
161
raise SystemExit("No recipients specified.")
162
buffer.write('To: %s\r\n' % (', '.join(o.to),))
163
if not requiredHeaders['date']:
164
buffer.write('Date: %s\r\n' % (smtp.rfc822date(),))
168
if o.recipientsFromHeaders:
169
for a in o.excludeAddresses:
176
o.body = StringIO.StringIO(buffer.getvalue() + sys.stdin.read())
181
@ivar allowUIDs: A list of UIDs which are allowed to send mail.
182
@ivar allowGIDs: A list of GIDs which are allowed to send mail.
183
@ivar denyUIDs: A list of UIDs which are not allowed to send mail.
184
@ivar denyGIDs: A list of GIDs which are not allowed to send mail.
186
@type defaultAccess: C{bool}
187
@ivar defaultAccess: C{True} if access will be allowed when no other access
188
control rule matches or C{False} if it will be denied in that case.
190
@ivar useraccess: Either C{'allow'} to check C{allowUID} first
191
or C{'deny'} to check C{denyUID} first.
193
@ivar groupaccess: Either C{'allow'} to check C{allowGID} first or
194
C{'deny'} to check C{denyGID} first.
196
@ivar identities: A C{dict} mapping hostnames to credentials to use when
197
sending mail to that host.
199
@ivar smarthost: C{None} or a hostname through which all outgoing mail will
202
@ivar domain: C{None} or the hostname with which to identify ourselves when
203
connecting to an MTA.
210
self.useraccess = 'deny'
211
self.groupaccess= 'deny'
214
self.smarthost = None
217
self.defaultAccess = True
220
def loadConfig(path):
222
# allow=uid1,uid2,...
226
# allow=gid1,gid2,...
230
# host1=username:password
231
# host2=username:password
234
# default_domain=x.y.z
238
if not os.access(path, os.R_OK):
248
for (section, a, d) in (('useraccess', au, du), ('groupaccess', ag, dg)):
249
if p.has_section(section):
250
for (mode, L) in (('allow', a), ('deny', d)):
251
if p.has_option(section, mode) and p.get(section, mode):
252
for id in p.get(section, mode).split(','):
256
log("Illegal %sID in [%s] section: %s", section[0].upper(), section, id)
259
order = p.get(section, 'order')
260
order = map(str.split, map(str.lower, order.split(',')))
261
if order[0] == 'allow':
262
setattr(c, section, 'allow')
264
setattr(c, section, 'deny')
266
if p.has_section('identity'):
267
for (host, up) in p.items('identity'):
268
parts = up.split(':', 1)
270
log("Illegal entry in [identity] section: %s", up)
272
p.identities[host] = parts
274
if p.has_section('addresses'):
275
if p.has_option('addresses', 'smarthost'):
276
c.smarthost = p.get('addresses', 'smarthost')
277
if p.has_option('addresses', 'default_domain'):
278
c.domain = p.get('addresses', 'default_domain')
291
def sendmail(host, options, ident):
292
d = smtp.sendmail(host, options.sender, options.to, options.body)
293
d.addCallbacks(success, failure)
296
def senderror(failure, options):
297
recipient = [options.sender]
298
sender = '"Internally Generated Message (%s)"<postmaster@%s>' % (sys.argv[0], smtp.DNSNAME)
299
error = StringIO.StringIO()
300
failure.printTraceback(file=error)
301
body = StringIO.StringIO(ERROR_FMT % error.getvalue())
303
d = smtp.sendmail('localhost', sender, recipient, body)
304
d.addBoth(lambda _: reactor.stop())
310
if conf.useraccess == 'deny':
311
if uid in conf.denyUIDs:
313
if uid in conf.allowUIDs:
316
if uid in conf.allowUIDs:
318
if uid in conf.denyUIDs:
321
if conf.groupaccess == 'deny':
322
if gid in conf.denyGIDs:
324
if gid in conf.allowGIDs:
327
if gid in conf.allowGIDs:
329
if gid in conf.denyGIDs:
332
return not conf.defaultAccess
335
o = parseOptions(sys.argv[1:])
336
gConf = loadConfig(GLOBAL_CFG)
337
lConf = loadConfig(LOCAL_CFG)
339
if deny(gConf) or deny(lConf):
340
log("Permission denied")
343
host = lConf.smarthost or gConf.smarthost or SMARTHOST
345
ident = gConf.identities.copy()
346
ident.update(lConf.identities)
349
smtp.DNSNAME = lConf.domain
351
smtp.DNSNAME = gConf.domain
353
sendmail(host, o, ident)
357
failed.printTraceback(file=sys.stderr)