# 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.

    >>> 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

## No approval

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

## 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"
    <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
    ...
    ... 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"
    <BLANKLINE>
    --AAA
    Content-Type: application/x-ignore
    <BLANKLINE>
    Approved: not the password
    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>

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"
    <BLANKLINE>
    --AAA
    Content-Type: application/x-ignore
    <BLANKLINE>
    Approved: super secret
    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 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"
    <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 (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--