1===================== 2Pre-approved postings 3===================== 4 5Messages can contain a pre-approval, which is used to bypass the normal 6message approval queue. This has several use cases: 7 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 10 normal email. 11 12 - An automated script can be programmed to send a message to an otherwise 13 moderated list. 14 15In order to support this, a mailing list can be given a *moderator password* 16which is shared among all the administrators. 17 18 >>> mlist = create_list('test@example.com') 19 20This password will not be stored in clear text, so it must be hashed using the 21configured hash protocol. 22 23 >>> mlist.moderator_password = config.password_context.encrypt( 24 ... 'super secret') 25 26The ``approved`` rule determines whether the message contains the proper 27approval or not. 28 29 >>> rule = config.rules['approved'] 30 >>> print(rule.name) 31 approved 32 33 34No approval 35=========== 36 37The preferred header to check for approval is ``Approved:``. If the message 38does not have this header, the rule will not match. 39 40 >>> msg = message_from_string("""\ 41 ... From: aperson@example.com 42 ... 43 ... An important message. 44 ... """) 45 >>> rule.check(mlist, msg, {}) 46 False 47 48If the rule has an ``Approved`` header, but the value of this header does not 49match the moderator password, the rule will not match. Note that the header 50must contain the clear text version of the password. 51 52 >>> msg['Approved'] = 'not the password' 53 >>> rule.check(mlist, msg, {}) 54 False 55 56 57The message is approved 58======================= 59 60By adding an ``Approved`` header with a matching password, the rule will 61match. 62 63 >>> del msg['approved'] 64 >>> msg['Approved'] = 'super secret' 65 >>> rule.check(mlist, msg, {}) 66 True 67 68 69Alternative headers 70=================== 71 72Other headers can be used to stash the moderator password. This rule also 73checks the ``Approve`` header. 74 75 >>> del msg['approved'] 76 >>> msg['Approve'] = 'super secret' 77 >>> rule.check(mlist, msg, {}) 78 True 79 80Similarly, an ``X-Approved`` header can be used. 81 82 >>> del msg['approve'] 83 >>> msg['X-Approved'] = 'super secret' 84 >>> rule.check(mlist, msg, {}) 85 True 86 87And finally, an ``X-Approve`` header can be used. 88 89 >>> del msg['x-approved'] 90 >>> msg['X-Approve'] = 'super secret' 91 >>> rule.check(mlist, msg, {}) 92 True 93 94 95Removal of header 96================= 97 98Technically, rules should not have side-effects, however this rule does remove 99the ``Approved`` header (LP: #973790) when it matches. 100 101 >>> del msg['x-approved'] 102 >>> msg['Approved'] = 'super secret' 103 >>> rule.check(mlist, msg, {}) 104 True 105 >>> print(msg['approved']) 106 None 107 108It also removes the header when it doesn't match. If the rule didn't do this, 109then the mailing list could be probed for its moderator password. 110 111 >>> msg['Approved'] = 'not the password' 112 >>> rule.check(mlist, msg, {}) 113 False 114 >>> print(msg['approved']) 115 None 116 117 118Using a pseudo-header 119===================== 120 121Mail programs have varying degrees to which they support custom headers like 122``Approved:``. For this reason, Mailman also supports using a 123*pseudo-header*, which is really just the first non-whitespace line in the 124payload of the message. If this pseudo-header looks like a matching 125``Approved:`` header, the message is similarly allowed to pass. 126 127 >>> msg = message_from_string("""\ 128 ... From: aperson@example.com 129 ... 130 ... Approved: super secret 131 ... An important message. 132 ... """) 133 >>> rule.check(mlist, msg, {}) 134 True 135 136The pseudo-header is always removed from the body of plain text messages. 137 138 >>> print(msg.as_string()) 139 From: aperson@example.com 140 Content-Transfer-Encoding: 7bit 141 MIME-Version: 1.0 142 Content-Type: text/plain; charset="us-ascii" 143 <BLANKLINE> 144 An important message. 145 <BLANKLINE> 146 147As before, a mismatch in the pseudo-header does not approve the message, but 148the pseudo-header line is still removed. 149:: 150 151 >>> msg = message_from_string("""\ 152 ... From: aperson@example.com 153 ... 154 ... Approved: not the password 155 ... An important message. 156 ... """) 157 >>> rule.check(mlist, msg, {}) 158 False 159 160 >>> print(msg.as_string()) 161 From: aperson@example.com 162 Content-Transfer-Encoding: 7bit 163 MIME-Version: 1.0 164 Content-Type: text/plain; charset="us-ascii" 165 <BLANKLINE> 166 An important message. 167 <BLANKLINE> 168 169 170MIME multipart support 171====================== 172 173Mailman searches for the pseudo-header as the first non-whitespace line in the 174first ``text/plain`` message part of the message. This allows the feature to 175be used with MIME documents. 176 177 >>> msg = message_from_string("""\ 178 ... From: aperson@example.com 179 ... MIME-Version: 1.0 180 ... Content-Type: multipart/mixed; boundary="AAA" 181 ... 182 ... --AAA 183 ... Content-Type: application/x-ignore 184 ... 185 ... Approved: not the password 186 ... The above line will be ignored. 187 ... 188 ... --AAA 189 ... Content-Type: text/plain 190 ... 191 ... Approved: super secret 192 ... An important message. 193 ... --AAA-- 194 ... """) 195 >>> rule.check(mlist, msg, {}) 196 True 197 198Like before, the pseudo-header is removed, but only from the text parts. 199 200 >>> print(msg.as_string()) 201 From: aperson@example.com 202 MIME-Version: 1.0 203 Content-Type: multipart/mixed; boundary="AAA" 204 <BLANKLINE> 205 --AAA 206 Content-Type: application/x-ignore 207 <BLANKLINE> 208 Approved: not the password 209 The above line will be ignored. 210 <BLANKLINE> 211 --AAA 212 Content-Transfer-Encoding: 7bit 213 MIME-Version: 1.0 214 Content-Type: text/plain; charset="us-ascii" 215 <BLANKLINE> 216 An important message. 217 --AAA-- 218 <BLANKLINE> 219 220If the correct password is in the non-``text/plain`` part, it is ignored. 221 222 >>> msg = message_from_string("""\ 223 ... From: aperson@example.com 224 ... MIME-Version: 1.0 225 ... Content-Type: multipart/mixed; boundary="AAA" 226 ... 227 ... --AAA 228 ... Content-Type: application/x-ignore 229 ... 230 ... Approved: super secret 231 ... The above line will be ignored. 232 ... 233 ... --AAA 234 ... Content-Type: text/plain 235 ... 236 ... Approved: not the password 237 ... An important message. 238 ... --AAA-- 239 ... """) 240 >>> rule.check(mlist, msg, {}) 241 False 242 243Pseudo-header is still stripped, but only from the ``text/plain`` part. 244 245 >>> print(msg.as_string()) 246 From: aperson@example.com 247 MIME-Version: 1.0 248 Content-Type: multipart/mixed; boundary="AAA" 249 <BLANKLINE> 250 --AAA 251 Content-Type: application/x-ignore 252 <BLANKLINE> 253 Approved: super secret 254 The above line will be ignored. 255 <BLANKLINE> 256 --AAA 257 Content-Transfer-Encoding: 7bit 258 MIME-Version: 1.0 259 Content-Type: text/plain; charset="us-ascii" 260 <BLANKLINE> 261 An important message. 262 --AAA-- 263 264 265Stripping text/html parts 266========================= 267 268Because some mail programs will include both a ``text/plain`` part and a 269``text/html`` alternative, the rule must search the alternatives and strip 270anything that looks like an ``Approved:`` header. 271 272 >>> msg = message_from_string("""\ 273 ... From: aperson@example.com 274 ... MIME-Version: 1.0 275 ... Content-Type: multipart/mixed; boundary="AAA" 276 ... 277 ... --AAA 278 ... Content-Type: text/html 279 ... 280 ... <html> 281 ... <head></head> 282 ... <body> 283 ... <b>Approved: super secret</b> 284 ... <p>The above line will be ignored. 285 ... </body> 286 ... </html> 287 ... 288 ... --AAA 289 ... Content-Type: text/plain 290 ... 291 ... Approved: super secret 292 ... An important message. 293 ... --AAA-- 294 ... """) 295 >>> rule.check(mlist, msg, {}) 296 True 297 298And the header-like text in the ``text/html`` part was stripped. 299 300 >>> print(msg.as_string()) 301 From: aperson@example.com 302 MIME-Version: 1.0 303 Content-Type: multipart/mixed; boundary="AAA" 304 <BLANKLINE> 305 --AAA 306 Content-Transfer-Encoding: 7bit 307 MIME-Version: 1.0 308 Content-Type: text/html; charset="us-ascii" 309 <BLANKLINE> 310 <html> 311 <head></head> 312 <body> 313 <b></b> 314 <p>The above line will be ignored. 315 </body> 316 </html> 317 <BLANKLINE> 318 --AAA 319 Content-Transfer-Encoding: 7bit 320 MIME-Version: 1.0 321 Content-Type: text/plain; charset="us-ascii" 322 <BLANKLINE> 323 An important message. 324 --AAA-- 325 <BLANKLINE> 326 327This is true even if the rule does not match (i.e. the incorrect password was 328given). 329:: 330 331 >>> msg = message_from_string("""\ 332 ... From: aperson@example.com 333 ... MIME-Version: 1.0 334 ... Content-Type: multipart/mixed; boundary="AAA" 335 ... 336 ... --AAA 337 ... Content-Type: text/html 338 ... 339 ... <html> 340 ... <head></head> 341 ... <body> 342 ... <b>Approved: not the password</b> 343 ... <p>The above line will be ignored. 344 ... </body> 345 ... </html> 346 ... 347 ... --AAA 348 ... Content-Type: text/plain 349 ... 350 ... Approved: not the password 351 ... An important message. 352 ... --AAA-- 353 ... """) 354 >>> rule.check(mlist, msg, {}) 355 False 356 357 >>> print(msg.as_string()) 358 From: aperson@example.com 359 MIME-Version: 1.0 360 Content-Type: multipart/mixed; boundary="AAA" 361 <BLANKLINE> 362 --AAA 363 Content-Transfer-Encoding: 7bit 364 MIME-Version: 1.0 365 Content-Type: text/html; charset="us-ascii" 366 <BLANKLINE> 367 <html> 368 <head></head> 369 <body> 370 <b></b> 371 <p>The above line will be ignored. 372 </body> 373 </html> 374 <BLANKLINE> 375 --AAA 376 Content-Transfer-Encoding: 7bit 377 MIME-Version: 1.0 378 Content-Type: text/plain; charset="us-ascii" 379 <BLANKLINE> 380 An important message. 381 --AAA-- 382 <BLANKLINE> 383