2
2
Pre-approved postings
3
3
=====================
5
Messages can contain a pre-approval, which is used to bypass the message
6
approval queue. This has several use cases:
8
- A list administrator can send an emergency message to the mailing list from
9
an unregistered address, say if they are away from their normal email.
11
- An automated script can be programmed to send a message to an otherwise
5
Messages can contain a pre-approval, which is used to bypass the normal
6
message approval queue. This has several use cases:
8
- A list administrator can send an emergency message to the mailing list
9
from an unregistered address, for example if they are away from their
12
- An automated script can be programmed to send a message to an otherwise
14
15
In order to support this, a mailing list can be given a *moderator password*
15
16
which is shared among all the administrators.
17
>>> mlist = create_list('_xtest@example.com')
18
>>> mlist.moderator_password = 'abcxyz'
18
>>> mlist = create_list('test@example.com')
20
This password will not be stored in clear text, so it must be hashed using the
21
configured hash protocol.
23
>>> from flufl.password import lookup, make_secret
24
>>> scheme = lookup(config.passwords.password_scheme.upper())
25
>>> mlist.moderator_password = make_secret('super secret', scheme)
20
27
The ``approved`` rule determines whether the message contains the proper
39
46
>>> rule.check(mlist, msg, {})
42
If the message has an ``Approve:``, ``Approved:``, ``X-Approve:``, or
43
``X-Approved:`` header with a value that does not match the moderator
44
password, then the rule does not match. However, the header is still removed.
47
>>> msg['Approve'] = '12345'
48
>>> rule.check(mlist, msg, {})
50
>>> print msg['approve']
53
>>> del msg['approve']
54
>>> msg['Approved'] = '12345'
55
>>> rule.check(mlist, msg, {})
57
>>> print msg['approved']
60
>>> del msg['approved']
61
>>> msg['X-Approve'] = '12345'
62
>>> rule.check(mlist, msg, {})
64
>>> print msg['x-approve']
67
>>> del msg['x-approve']
68
>>> msg['X-Approved'] = '12345'
69
>>> rule.check(mlist, msg, {})
71
>>> print msg['x-approved']
74
>>> del msg['x-approved']
77
Using an approval header
78
========================
80
If the moderator password is given in an ``Approve:`` header, then the rule
81
matches, and the ``Approve:`` header is stripped.
83
>>> msg['Approve'] = 'abcxyz'
84
>>> rule.check(mlist, msg, {})
86
>>> print msg['approve']
89
Similarly, for the ``Approved:`` header.
91
>>> del msg['approve']
92
>>> msg['Approved'] = 'abcxyz'
93
>>> rule.check(mlist, msg, {})
95
>>> print msg['approved']
98
The headers ``X-Approve:`` and ``X-Approved:`` are treated the same way.
101
>>> del msg['approved']
102
>>> msg['X-Approve'] = 'abcxyz'
103
>>> rule.check(mlist, msg, {})
105
>>> print msg['x-approve']
108
>>> del msg['x-approve']
109
>>> msg['X-Approved'] = 'abcxyz'
110
>>> rule.check(mlist, msg, {})
112
>>> print msg['x-approved']
115
>>> del msg['x-approved']
49
If the rule has an ``Approved`` header, but the value of this header does not
50
match the moderator password, the rule will not match. Note that the header
51
must contain the clear text version of the password.
53
>>> msg['Approved'] = 'not the password'
54
>>> rule.check(mlist, msg, {})
58
The message is approved
59
=======================
61
By adding an ``Approved`` header with a matching password, the rule will
64
>>> del msg['approved']
65
>>> msg['Approved'] = 'super secret'
66
>>> rule.check(mlist, msg, {})
73
Other headers can be used to stash the moderator password. This rule also
74
checks the ``Approve`` header.
76
>>> del msg['approved']
77
>>> msg['Approve'] = 'super secret'
78
>>> rule.check(mlist, msg, {})
81
Similarly, an ``X-Approved`` header can be used.
83
>>> del msg['approve']
84
>>> msg['X-Approved'] = 'super secret'
85
>>> rule.check(mlist, msg, {})
88
And finally, an ``X-Approve`` header can be used.
90
>>> del msg['x-approved']
91
>>> msg['X-Approve'] = 'super secret'
92
>>> rule.check(mlist, msg, {})
99
Technically, rules should not have side-effects, however this rule does remove
100
the ``Approved`` header (LP: #973790) when it matches.
102
>>> del msg['x-approved']
103
>>> msg['Approved'] = 'super secret'
104
>>> rule.check(mlist, msg, {})
106
>>> print msg['approved']
109
It also removes the header when it doesn't match. If the rule didn't do this,
110
then the mailing list could be probed for its moderator password.
112
>>> msg['Approved'] = 'not the password'
113
>>> rule.check(mlist, msg, {})
115
>>> print msg['approved']
118
119
Using a pseudo-header
119
120
=====================
121
Different mail user agents have varying degrees to which they support custom
122
headers like ``Approve:`` and ``Approved:``. For this reason, Mailman also
123
supports using a *pseudo-header*, which is really just the first
124
non-whitespace line in the payload of the message. If this pseudo-header
125
looks like a matching ``Approve:`` or ``Approved:`` header, the message is
126
similarly allowed to pass.
128
>>> msg = message_from_string("""\
129
... From: aperson@example.com
132
... An important message.
134
>>> rule.check(mlist, msg, {})
137
The pseudo-header is removed.
139
>>> print msg.as_string()
140
From: aperson@example.com
141
Content-Transfer-Encoding: 7bit
143
Content-Type: text/plain; charset="us-ascii"
145
An important message.
148
Similarly for the ``Approved:`` header.
151
>>> msg = message_from_string("""\
152
... From: aperson@example.com
155
... An important message.
157
>>> rule.check(mlist, msg, {})
122
Mail programs have varying degrees to which they support custom headers like
123
``Approved:``. For this reason, Mailman also supports using a
124
*pseudo-header*, which is really just the first non-whitespace line in the
125
payload of the message. If this pseudo-header looks like a matching
126
``Approved:`` header, the message is similarly allowed to pass.
128
>>> msg = message_from_string("""\
129
... From: aperson@example.com
131
... Approved: super secret
132
... An important message.
134
>>> rule.check(mlist, msg, {})
137
The pseudo-header is always removed from the body of plain text messages.
160
139
>>> print msg.as_string()
161
140
From: aperson@example.com
249
207
Content-Type: application/x-ignore
252
The above line will be ignored.
255
Content-Transfer-Encoding: 7bit
257
Content-Type: text/plain; charset="us-ascii"
259
An important message.
263
The same goes for the ``Approved:`` message.
265
>>> msg = message_from_string("""\
266
... From: aperson@example.com
267
... MIME-Version: 1.0
268
... Content-Type: multipart/mixed; boundary="AAA"
271
... Content-Type: application/x-ignore
274
... The above line will be ignored.
277
... Content-Type: text/plain
280
... An important message.
283
>>> rule.check(mlist, msg, {})
286
And the header is removed.
288
>>> print msg.as_string()
289
From: aperson@example.com
291
Content-Type: multipart/mixed; boundary="AAA"
294
Content-Type: application/x-ignore
297
The above line will be ignored.
300
Content-Transfer-Encoding: 7bit
302
Content-Type: text/plain; charset="us-ascii"
304
An important message.
308
Here, the correct password is in the non-``text/plain`` part, so it is ignored.
310
>>> msg = message_from_string("""\
311
... From: aperson@example.com
312
... MIME-Version: 1.0
313
... Content-Type: multipart/mixed; boundary="AAA"
316
... Content-Type: application/x-ignore
319
... The above line will be ignored.
322
... Content-Type: text/plain
325
... An important message.
328
>>> rule.check(mlist, msg, {})
331
And yet the pseudo-header is still stripped.
333
>>> print msg.as_string()
334
From: aperson@example.com
336
Content-Type: multipart/mixed; boundary="AAA"
339
Content-Type: application/x-ignore
342
The above line will be ignored.
345
Content-Transfer-Encoding: 7bit
347
Content-Type: text/plain; charset="us-ascii"
349
An important message.
352
As before, the same goes for the ``Approved:`` header.
354
>>> msg = message_from_string("""\
355
... From: aperson@example.com
356
... MIME-Version: 1.0
357
... Content-Type: multipart/mixed; boundary="AAA"
360
... Content-Type: application/x-ignore
363
... The above line will be ignored.
366
... Content-Type: text/plain
369
... An important message.
372
>>> rule.check(mlist, msg, {})
375
And the pseudo-header is removed.
377
>>> print msg.as_string()
378
From: aperson@example.com
380
Content-Type: multipart/mixed; boundary="AAA"
383
Content-Type: application/x-ignore
209
Approved: not the password
210
The above line will be ignored.
213
Content-Transfer-Encoding: 7bit
215
Content-Type: text/plain; charset="us-ascii"
217
An important message.
221
If the correct password is in the non-``text/plain`` part, it is ignored.
223
>>> msg = message_from_string("""\
224
... From: aperson@example.com
225
... MIME-Version: 1.0
226
... Content-Type: multipart/mixed; boundary="AAA"
229
... Content-Type: application/x-ignore
231
... Approved: super secret
232
... The above line will be ignored.
235
... Content-Type: text/plain
237
... Approved: not the password
238
... An important message.
241
>>> rule.check(mlist, msg, {})
244
Pseudo-header is still stripped, but only from the ``text/plain`` part.
246
>>> print msg.as_string()
247
From: aperson@example.com
249
Content-Type: multipart/mixed; boundary="AAA"
252
Content-Type: application/x-ignore
254
Approved: super secret
386
255
The above line will be ignored.