~blamar/+junk/openstack-api-arrrg

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/mail/scripts/mailmail.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.mail.test.test_mailmail -*-
 
2
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""
 
6
Implementation module for the I{mailmail} command.
 
7
"""
 
8
 
 
9
import os
 
10
import sys
 
11
import rfc822
 
12
import getpass
 
13
from ConfigParser import ConfigParser
 
14
 
 
15
try:
 
16
    import cStringIO as StringIO
 
17
except:
 
18
    import StringIO
 
19
 
 
20
from twisted.internet import reactor
 
21
from twisted.mail import smtp
 
22
 
 
23
GLOBAL_CFG = "/etc/mailmail"
 
24
LOCAL_CFG = os.path.expanduser("~/.twisted/mailmail")
 
25
SMARTHOST = '127.0.0.1'
 
26
 
 
27
ERROR_FMT = """\
 
28
Subject: Failed Message Delivery
 
29
 
 
30
  Message delivery failed.  The following occurred:
 
31
 
 
32
  %s
 
33
--
 
34
The Twisted sendmail application.
 
35
"""
 
36
 
 
37
def log(message, *args):
 
38
    sys.stderr.write(str(message) % args + '\n')
 
39
 
 
40
class Options:
 
41
    """
 
42
    @type to: C{list} of C{str}
 
43
    @ivar to: The addresses to which to deliver this message.
 
44
 
 
45
    @type sender: C{str}
 
46
    @ivar sender: The address from which this message is being sent.
 
47
 
 
48
    @type body: C{file}
 
49
    @ivar body: The object from which the message is to be read.
 
50
    """
 
51
 
 
52
def getlogin():
 
53
    try:
 
54
        return os.getlogin()
 
55
    except:
 
56
        return getpass.getuser()
 
57
 
 
58
 
 
59
_unsupportedOption = SystemExit("Unsupported option.")
 
60
 
 
61
def parseOptions(argv):
 
62
    o = Options()
 
63
    o.to = [e for e in argv if not e.startswith('-')]
 
64
    o.sender = getlogin()
 
65
 
 
66
    # Just be very stupid
 
67
 
 
68
    # Skip -bm -- it is the default
 
69
 
 
70
    # -bp lists queue information.  Screw that.
 
71
    if '-bp' in argv:
 
72
        raise _unsupportedOption
 
73
 
 
74
    # -bs makes sendmail use stdin/stdout as its transport.  Screw that.
 
75
    if '-bs' in argv:
 
76
        raise _unsupportedOption
 
77
 
 
78
    # -F sets who the mail is from, but is overridable by the From header
 
79
    if '-F' in argv:
 
80
        o.sender = argv[argv.index('-F') + 1]
 
81
        o.to.remove(o.sender)
 
82
 
 
83
    # -i and -oi makes us ignore lone "."
 
84
    if ('-i' in argv) or ('-oi' in argv):
 
85
        raise _unsupportedOption
 
86
 
 
87
    # -odb is background delivery
 
88
    if '-odb' in argv:
 
89
        o.background = True
 
90
    else:
 
91
        o.background = False
 
92
 
 
93
    # -odf is foreground delivery
 
94
    if '-odf' in argv:
 
95
        o.background = False
 
96
    else:
 
97
        o.background = True
 
98
 
 
99
    # -oem and -em cause errors to be mailed back to the sender.
 
100
    # It is also the default.
 
101
 
 
102
    # -oep and -ep cause errors to be printed to stderr
 
103
    if ('-oep' in argv) or ('-ep' in argv):
 
104
        o.printErrors = True
 
105
    else:
 
106
        o.printErrors = False
 
107
 
 
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.
 
110
    if '-om' in argv:
 
111
        raise _unsupportedOption
 
112
 
 
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.
 
115
    if '-t' in argv:
 
116
        o.recipientsFromHeaders = True
 
117
        o.excludeAddresses = o.to
 
118
        o.to = []
 
119
    else:
 
120
        o.recipientsFromHeaders = False
 
121
        o.exludeAddresses = []
 
122
 
 
123
    requiredHeaders = {
 
124
        'from': [],
 
125
        'to': [],
 
126
        'cc': [],
 
127
        'bcc': [],
 
128
        'date': [],
 
129
    }
 
130
 
 
131
    headers = []
 
132
    buffer = StringIO.StringIO()
 
133
    while 1:
 
134
        write = 1
 
135
        line = sys.stdin.readline()
 
136
        if not line.strip():
 
137
            break
 
138
 
 
139
        hdrs = line.split(': ', 1)
 
140
 
 
141
        hdr = hdrs[0].lower()
 
142
        if o.recipientsFromHeaders and hdr in ('to', 'cc', 'bcc'):
 
143
            o.to.extend([
 
144
                a[1] for a in rfc822.AddressList(hdrs[1]).addresslist
 
145
            ])
 
146
            if hdr == 'bcc':
 
147
                write = 0
 
148
        elif hdr == 'from':
 
149
            o.sender = rfc822.parseaddr(hdrs[1])[1]
 
150
 
 
151
        if hdr in requiredHeaders:
 
152
            requiredHeaders[hdr].append(hdrs[1])
 
153
 
 
154
        if write:
 
155
            buffer.write(line)
 
156
 
 
157
    if not requiredHeaders['from']:
 
158
        buffer.write('From: %s\r\n' % (o.sender,))
 
159
    if not requiredHeaders['to']:
 
160
        if not o.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(),))
 
165
 
 
166
    buffer.write(line)
 
167
 
 
168
    if o.recipientsFromHeaders:
 
169
        for a in o.excludeAddresses:
 
170
            try:
 
171
                o.to.remove(a)
 
172
            except:
 
173
                pass
 
174
 
 
175
    buffer.seek(0, 0)
 
176
    o.body = StringIO.StringIO(buffer.getvalue() + sys.stdin.read())
 
177
    return o
 
178
 
 
179
class Configuration:
 
180
    """
 
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.
 
185
 
 
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.
 
189
 
 
190
    @ivar useraccess: Either C{'allow'} to check C{allowUID} first
 
191
    or C{'deny'} to check C{denyUID} first.
 
192
 
 
193
    @ivar groupaccess: Either C{'allow'} to check C{allowGID} first or
 
194
    C{'deny'} to check C{denyGID} first.
 
195
 
 
196
    @ivar identities: A C{dict} mapping hostnames to credentials to use when
 
197
    sending mail to that host.
 
198
 
 
199
    @ivar smarthost: C{None} or a hostname through which all outgoing mail will
 
200
    be sent.
 
201
 
 
202
    @ivar domain: C{None} or the hostname with which to identify ourselves when
 
203
    connecting to an MTA.
 
204
    """
 
205
    def __init__(self):
 
206
        self.allowUIDs = []
 
207
        self.denyUIDs = []
 
208
        self.allowGIDs = []
 
209
        self.denyGIDs = []
 
210
        self.useraccess = 'deny'
 
211
        self.groupaccess= 'deny'
 
212
 
 
213
        self.identities = {}
 
214
        self.smarthost = None
 
215
        self.domain = None
 
216
 
 
217
        self.defaultAccess = True
 
218
 
 
219
 
 
220
def loadConfig(path):
 
221
    # [useraccess]
 
222
    # allow=uid1,uid2,...
 
223
    # deny=uid1,uid2,...
 
224
    # order=allow,deny
 
225
    # [groupaccess]
 
226
    # allow=gid1,gid2,...
 
227
    # deny=gid1,gid2,...
 
228
    # order=deny,allow
 
229
    # [identity]
 
230
    # host1=username:password
 
231
    # host2=username:password
 
232
    # [addresses]
 
233
    # smarthost=a.b.c.d
 
234
    # default_domain=x.y.z
 
235
 
 
236
    c = Configuration()
 
237
 
 
238
    if not os.access(path, os.R_OK):
 
239
        return c
 
240
 
 
241
    p = ConfigParser()
 
242
    p.read(path)
 
243
 
 
244
    au = c.allowUIDs
 
245
    du = c.denyUIDs
 
246
    ag = c.allowGIDs
 
247
    dg = c.denyGIDs
 
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(','):
 
253
                        try:
 
254
                            id = int(id)
 
255
                        except ValueError:
 
256
                            log("Illegal %sID in [%s] section: %s", section[0].upper(), section, id)
 
257
                        else:
 
258
                            L.append(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')
 
263
            else:
 
264
                setattr(c, section, 'deny')
 
265
 
 
266
    if p.has_section('identity'):
 
267
        for (host, up) in p.items('identity'):
 
268
            parts = up.split(':', 1)
 
269
            if len(parts) != 2:
 
270
                log("Illegal entry in [identity] section: %s", up)
 
271
                continue
 
272
            p.identities[host] = parts
 
273
 
 
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')
 
279
 
 
280
    return c
 
281
 
 
282
def success(result):
 
283
    reactor.stop()
 
284
 
 
285
failed = None
 
286
def failure(f):
 
287
    global failed
 
288
    reactor.stop()
 
289
    failed = f
 
290
 
 
291
def sendmail(host, options, ident):
 
292
    d = smtp.sendmail(host, options.sender, options.to, options.body)
 
293
    d.addCallbacks(success, failure)
 
294
    reactor.run()
 
295
 
 
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())
 
302
 
 
303
    d = smtp.sendmail('localhost', sender, recipient, body)
 
304
    d.addBoth(lambda _: reactor.stop())
 
305
 
 
306
def deny(conf):
 
307
    uid = os.getuid()
 
308
    gid = os.getgid()
 
309
 
 
310
    if conf.useraccess == 'deny':
 
311
        if uid in conf.denyUIDs:
 
312
            return True
 
313
        if uid in conf.allowUIDs:
 
314
            return False
 
315
    else:
 
316
        if uid in conf.allowUIDs:
 
317
            return False
 
318
        if uid in conf.denyUIDs:
 
319
            return True
 
320
 
 
321
    if conf.groupaccess == 'deny':
 
322
        if gid in conf.denyGIDs:
 
323
            return True
 
324
        if gid in conf.allowGIDs:
 
325
            return False
 
326
    else:
 
327
        if gid in conf.allowGIDs:
 
328
            return False
 
329
        if gid in conf.denyGIDs:
 
330
            return True
 
331
 
 
332
    return not conf.defaultAccess
 
333
 
 
334
def run():
 
335
    o = parseOptions(sys.argv[1:])
 
336
    gConf = loadConfig(GLOBAL_CFG)
 
337
    lConf = loadConfig(LOCAL_CFG)
 
338
 
 
339
    if deny(gConf) or deny(lConf):
 
340
        log("Permission denied")
 
341
        return
 
342
 
 
343
    host = lConf.smarthost or gConf.smarthost or SMARTHOST
 
344
 
 
345
    ident = gConf.identities.copy()
 
346
    ident.update(lConf.identities)
 
347
 
 
348
    if lConf.domain:
 
349
        smtp.DNSNAME = lConf.domain
 
350
    elif gConf.domain:
 
351
        smtp.DNSNAME = gConf.domain
 
352
 
 
353
    sendmail(host, o, ident)
 
354
 
 
355
    if failed:
 
356
        if o.printErrors:
 
357
            failed.printTraceback(file=sys.stderr)
 
358
            raise SystemExit(1)
 
359
        else:
 
360
            senderror(failed, o)