Pre-approved postings¶
Messages can contain a pre-approval, which is used to bypass the normal message approval queue. This has several use cases:
A list administrator can send an emergency message to the mailing list from an unregistered address, for example if they are away from their normal email.
An automated script can be programmed to send a message to an otherwise moderated list.
In order to support this, a mailing list can be given a moderator password which is shared among all the administrators.
>>> from mailman.app.lifecycle import create_list
>>> mlist = create_list('test@example.com')
This password will not be stored in clear text, so it must be hashed using the configured hash protocol.
>>> from mailman.config import config
>>> mlist.moderator_password = config.password_context.encrypt(
... 'super secret')
The approved
rule determines whether the message contains the proper
approval or not.
>>> from mailman.config import config
>>> rule = config.rules['approved']
>>> print(rule.name)
approved
No approval¶
The preferred header to check for approval is Approved:
. If the message
does not have this header, the rule will not match.
>>> from mailman.testing.helpers import (specialized_message_from_string
... as message_from_string)
>>> msg = message_from_string("""\
... From: aperson@example.com
...
... An important message.
... """)
>>> rule.check(mlist, msg, {})
False
If the rule has an Approved
header, but the value of this header does not
match the moderator password, the rule will not match. Note that the header
must contain the clear text version of the password.
>>> msg['Approved'] = 'not the password'
>>> rule.check(mlist, msg, {})
False
The message is approved¶
By adding an Approved
header with a matching password, the rule will
match.
>>> del msg['approved']
>>> msg['Approved'] = 'super secret'
>>> rule.check(mlist, msg, {})
True
Alternative headers¶
Other headers can be used to stash the moderator password. This rule also
checks the Approve
header.
>>> del msg['approved']
>>> msg['Approve'] = 'super secret'
>>> rule.check(mlist, msg, {})
True
Similarly, an X-Approved
header can be used.
>>> del msg['approve']
>>> msg['X-Approved'] = 'super secret'
>>> rule.check(mlist, msg, {})
True
And finally, an X-Approve
header can be used.
>>> del msg['x-approved']
>>> msg['X-Approve'] = 'super secret'
>>> rule.check(mlist, msg, {})
True
Removal of header¶
Technically, rules should not have side-effects, however this rule does remove
the Approved
header (LP: #973790) when it matches.
>>> del msg['x-approved']
>>> msg['Approved'] = 'super secret'
>>> rule.check(mlist, msg, {})
True
>>> print(msg['approved'])
None
It also removes the header when it doesn’t match. If the rule didn’t do this, then the mailing list could be probed for its moderator password.
>>> msg['Approved'] = 'not the password'
>>> rule.check(mlist, msg, {})
False
>>> print(msg['approved'])
None
Using a pseudo-header¶
Mail programs have varying degrees to which they support custom headers like
Approved:
. For this reason, Mailman also supports using a
pseudo-header, which is really just the first non-whitespace line in the
payload of the message. If this pseudo-header looks like a matching
Approved:
header, the message is similarly allowed to pass.
>>> msg = message_from_string("""\
... From: aperson@example.com
...
... Approved: super secret
... An important message.
... """)
>>> rule.check(mlist, msg, {})
True
The pseudo-header is always removed from the body of plain text messages.
>>> print(msg.as_string())
From: aperson@example.com
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
An important message.
As before, a mismatch in the pseudo-header does not approve the message, but the pseudo-header line is still removed.
>>> msg = message_from_string("""\
... From: aperson@example.com
...
... Approved: not the password
... An important message.
... """)
>>> rule.check(mlist, msg, {})
False
>>> print(msg.as_string())
From: aperson@example.com
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
An important message.
MIME multipart support¶
Mailman searches for the pseudo-header as the first non-whitespace line in the
first text/plain
message part of the message. This allows the feature to
be used with MIME documents.
>>> msg = message_from_string("""\
... From: aperson@example.com
... MIME-Version: 1.0
... Content-Type: multipart/mixed; boundary="AAA"
...
... --AAA
... Content-Type: application/x-ignore
...
... Approved: not the password
... The above line will be ignored.
...
... --AAA
... Content-Type: text/plain
...
... Approved: super secret
... An important message.
... --AAA--
... """)
>>> rule.check(mlist, msg, {})
True
Like before, the pseudo-header is removed, but only from the text parts.
>>> print(msg.as_string())
From: aperson@example.com
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="AAA"
--AAA
Content-Type: application/x-ignore
Approved: not the password
The above line will be ignored.
--AAA
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
An important message.
--AAA--
If the correct password is in the non-text/plain
part, it is ignored.
>>> msg = message_from_string("""\
... From: aperson@example.com
... MIME-Version: 1.0
... Content-Type: multipart/mixed; boundary="AAA"
...
... --AAA
... Content-Type: application/x-ignore
...
... Approved: super secret
... The above line will be ignored.
...
... --AAA
... Content-Type: text/plain
...
... Approved: not the password
... An important message.
... --AAA--
... """)
>>> rule.check(mlist, msg, {})
False
Pseudo-header is still stripped, but only from the text/plain
part.
>>> print(msg.as_string())
From: aperson@example.com
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="AAA"
--AAA
Content-Type: application/x-ignore
Approved: super secret
The above line will be ignored.
--AAA
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
An important message.
--AAA--
Stripping text/html parts¶
Because some mail programs will include both a text/plain
part and a
text/html
alternative, the rule must search the alternatives and strip
anything that looks like an Approved:
header.
>>> msg = message_from_string("""\
... From: aperson@example.com
... MIME-Version: 1.0
... Content-Type: multipart/mixed; boundary="AAA"
...
... --AAA
... Content-Type: text/html
...
... <html>
... <head></head>
... <body>
... <b>Approved: super secret</b>
... <p>The above line will be ignored.
... </body>
... </html>
...
... --AAA
... Content-Type: text/plain
...
... Approved: super secret
... An important message.
... --AAA--
... """)
>>> rule.check(mlist, msg, {})
True
And the header-like text in the text/html
part was stripped.
>>> print(msg.as_string())
From: aperson@example.com
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="AAA"
--AAA
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
Content-Type: text/html; charset="us-ascii"
<html>
<head></head>
<body>
<b></b>
<p>The above line will be ignored.
</body>
</html>
--AAA
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
An important message.
--AAA--
This is true even if the rule does not match (i.e. the incorrect password was given).
>>> msg = message_from_string("""\
... From: aperson@example.com
... MIME-Version: 1.0
... Content-Type: multipart/mixed; boundary="AAA"
...
... --AAA
... Content-Type: text/html
...
... <html>
... <head></head>
... <body>
... <b>Approved: not the password</b>
... <p>The above line will be ignored.
... </body>
... </html>
...
... --AAA
... Content-Type: text/plain
...
... Approved: not the password
... An important message.
... --AAA--
... """)
>>> rule.check(mlist, msg, {})
False
>>> print(msg.as_string())
From: aperson@example.com
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="AAA"
--AAA
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
Content-Type: text/html; charset="us-ascii"
<html>
<head></head>
<body>
<b></b>
<p>The above line will be ignored.
</body>
</html>
--AAA
Content-Transfer-Encoding: 7bit
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
An important message.
--AAA--