summaryrefslogtreecommitdiff
path: root/src/mailman/rules/docs/approved.rst
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/rules/docs/approved.rst')
-rw-r--r--src/mailman/rules/docs/approved.rst514
1 files changed, 514 insertions, 0 deletions
diff --git a/src/mailman/rules/docs/approved.rst b/src/mailman/rules/docs/approved.rst
new file mode 100644
index 000000000..3e1206563
--- /dev/null
+++ b/src/mailman/rules/docs/approved.rst
@@ -0,0 +1,514 @@
+=====================
+Pre-approved postings
+=====================
+
+Messages can contain a pre-approval, which is used to bypass the message
+approval queue. This has several use cases:
+
+- A list administrator can send an emergency message to the mailing list from
+ an unregistered address, say 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('_xtest@example.com')
+ >>> mlist.moderator_password = 'abcxyz'
+
+The ``approved`` rule determines whether the message contains the proper
+approval or not.
+
+ >>> rule = config.rules['approved']
+ >>> print rule.name
+ approved
+
+
+No approval
+===========
+
+If the message has no ``Approve:`` or ``Approved:`` header (or their ``X-``
+equivalents), then the rule does not match.
+
+ >>> msg = message_from_string("""\
+ ... From: aperson@example.com
+ ...
+ ... An important message.
+ ... """)
+ >>> rule.check(mlist, msg, {})
+ False
+
+If the message has an ``Approve:``, ``Approved:``, ``X-Approve:``, or
+``X-Approved:`` header with a value that does not match the moderator
+password, then the rule does not match. However, the header is still removed.
+::
+
+ >>> msg['Approve'] = '12345'
+ >>> rule.check(mlist, msg, {})
+ False
+ >>> print msg['approve']
+ None
+
+ >>> del msg['approve']
+ >>> msg['Approved'] = '12345'
+ >>> rule.check(mlist, msg, {})
+ False
+ >>> print msg['approved']
+ None
+
+ >>> del msg['approved']
+ >>> msg['X-Approve'] = '12345'
+ >>> rule.check(mlist, msg, {})
+ False
+ >>> print msg['x-approve']
+ None
+
+ >>> del msg['x-approve']
+ >>> msg['X-Approved'] = '12345'
+ >>> rule.check(mlist, msg, {})
+ False
+ >>> print msg['x-approved']
+ None
+
+ >>> del msg['x-approved']
+
+
+Using an approval header
+========================
+
+If the moderator password is given in an ``Approve:`` header, then the rule
+matches, and the ``Approve:`` header is stripped.
+
+ >>> msg['Approve'] = 'abcxyz'
+ >>> rule.check(mlist, msg, {})
+ True
+ >>> print msg['approve']
+ None
+
+Similarly, for the ``Approved:`` header.
+
+ >>> del msg['approve']
+ >>> msg['Approved'] = 'abcxyz'
+ >>> rule.check(mlist, msg, {})
+ True
+ >>> print msg['approved']
+ None
+
+The headers ``X-Approve:`` and ``X-Approved:`` are treated the same way.
+::
+
+ >>> del msg['approved']
+ >>> msg['X-Approve'] = 'abcxyz'
+ >>> rule.check(mlist, msg, {})
+ True
+ >>> print msg['x-approve']
+ None
+
+ >>> del msg['x-approve']
+ >>> msg['X-Approved'] = 'abcxyz'
+ >>> rule.check(mlist, msg, {})
+ True
+ >>> print msg['x-approved']
+ None
+
+ >>> del msg['x-approved']
+
+
+Using a pseudo-header
+=====================
+
+Different mail user agents have varying degrees to which they support custom
+headers like ``Approve:`` and ``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 ``Approve:`` or ``Approved:`` header, the message is
+similarly allowed to pass.
+
+ >>> msg = message_from_string("""\
+ ... From: aperson@example.com
+ ...
+ ... Approve: abcxyz
+ ... An important message.
+ ... """)
+ >>> rule.check(mlist, msg, {})
+ True
+
+The pseudo-header is removed.
+
+ >>> print msg.as_string()
+ From: aperson@example.com
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ <BLANKLINE>
+ An important message.
+ <BLANKLINE>
+
+Similarly for the ``Approved:`` header.
+::
+
+ >>> msg = message_from_string("""\
+ ... From: aperson@example.com
+ ...
+ ... Approved: abcxyz
+ ... An important message.
+ ... """)
+ >>> rule.check(mlist, msg, {})
+ True
+
+ >>> print msg.as_string()
+ From: aperson@example.com
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ <BLANKLINE>
+ An important message.
+ <BLANKLINE>
+
+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
+ ...
+ ... Approve: 123456
+ ... 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"
+ <BLANKLINE>
+ An important message.
+ <BLANKLINE>
+
+Similarly for the ``Approved:`` header.
+::
+
+ >>> msg = message_from_string("""\
+ ... From: aperson@example.com
+ ...
+ ... Approved: 123456
+ ... 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"
+ <BLANKLINE>
+ An important message.
+ <BLANKLINE>
+
+
+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
+ ...
+ ... Approve: 123456
+ ... The above line will be ignored.
+ ...
+ ... --AAA
+ ... Content-Type: text/plain
+ ...
+ ... Approve: abcxyz
+ ... 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"
+ <BLANKLINE>
+ --AAA
+ Content-Type: application/x-ignore
+ <BLANKLINE>
+ Approve: 123456
+ The above line will be ignored.
+ <BLANKLINE>
+ --AAA
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ <BLANKLINE>
+ An important message.
+ --AAA--
+ <BLANKLINE>
+
+The same goes for the ``Approved:`` message.
+
+ >>> 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: 123456
+ ... The above line will be ignored.
+ ...
+ ... --AAA
+ ... Content-Type: text/plain
+ ...
+ ... Approved: abcxyz
+ ... An important message.
+ ... --AAA--
+ ... """)
+ >>> rule.check(mlist, msg, {})
+ True
+
+And the header is removed.
+
+ >>> print msg.as_string()
+ From: aperson@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="AAA"
+ <BLANKLINE>
+ --AAA
+ Content-Type: application/x-ignore
+ <BLANKLINE>
+ Approved: 123456
+ The above line will be ignored.
+ <BLANKLINE>
+ --AAA
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ <BLANKLINE>
+ An important message.
+ --AAA--
+ <BLANKLINE>
+
+Here, the correct password is in the non-``text/plain`` part, so 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
+ ...
+ ... Approve: abcxyz
+ ... The above line will be ignored.
+ ...
+ ... --AAA
+ ... Content-Type: text/plain
+ ...
+ ... Approve: 123456
+ ... An important message.
+ ... --AAA--
+ ... """)
+ >>> rule.check(mlist, msg, {})
+ False
+
+And yet the pseudo-header is still stripped.
+
+ >>> print msg.as_string()
+ From: aperson@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="AAA"
+ <BLANKLINE>
+ --AAA
+ Content-Type: application/x-ignore
+ <BLANKLINE>
+ Approve: abcxyz
+ The above line will be ignored.
+ <BLANKLINE>
+ --AAA
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ <BLANKLINE>
+ An important message.
+ --AAA--
+
+As before, the same goes for the ``Approved:`` header.
+
+ >>> 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: abcxyz
+ ... The above line will be ignored.
+ ...
+ ... --AAA
+ ... Content-Type: text/plain
+ ...
+ ... Approved: 123456
+ ... An important message.
+ ... --AAA--
+ ... """)
+ >>> rule.check(mlist, msg, {})
+ False
+
+And the pseudo-header is removed.
+
+ >>> print msg.as_string()
+ From: aperson@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="AAA"
+ <BLANKLINE>
+ --AAA
+ Content-Type: application/x-ignore
+ <BLANKLINE>
+ Approved: abcxyz
+ The above line will be ignored.
+ <BLANKLINE>
+ --AAA
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ <BLANKLINE>
+ An important message.
+ --AAA--
+
+
+Stripping text/html parts
+=========================
+
+Because some mail readers will include both a ``text/plain`` part and a
+``text/html`` alternative, the ``approved`` rule has to search the
+alternatives and strip anything that looks like an ``Approve:`` or
+``Approved:`` headers.
+
+ >>> 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: abcxyz</b>
+ ... <p>The above line will be ignored.
+ ... </body>
+ ... </html>
+ ...
+ ... --AAA
+ ... Content-Type: text/plain
+ ...
+ ... Approved: abcxyz
+ ... 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"
+ <BLANKLINE>
+ --AAA
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/html; charset="us-ascii"
+ <BLANKLINE>
+ <html>
+ <head></head>
+ <body>
+ <b></b>
+ <p>The above line will be ignored.
+ </body>
+ </html>
+ <BLANKLINE>
+ --AAA
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ <BLANKLINE>
+ An important message.
+ --AAA--
+ <BLANKLINE>
+
+This is true even if the rule does not match.
+::
+
+ >>> 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>Approve: 123456</b>
+ ... <p>The above line will be ignored.
+ ... </body>
+ ... </html>
+ ...
+ ... --AAA
+ ... Content-Type: text/plain
+ ...
+ ... Approve: 123456
+ ... 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"
+ <BLANKLINE>
+ --AAA
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/html; charset="us-ascii"
+ <BLANKLINE>
+ <html>
+ <head></head>
+ <body>
+ <b></b>
+ <p>The above line will be ignored.
+ </body>
+ </html>
+ <BLANKLINE>
+ --AAA
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ <BLANKLINE>
+ An important message.
+ --AAA--
+ <BLANKLINE>