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.
>>> 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.
>>> mlist.moderator_password = config.password_context.encrypt(
... 'super secret')
The approved rule determines whether the message contains the proper approval or not.
>>> rule = config.rules['approved']
>>> print(rule.name)
approved
The preferred header to check for approval is Approved:. If the message does not have this header, the rule will not match.
>>> 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
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
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
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
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.
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--
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--