1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
|
<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Postfix After-Queue Content Filter </title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel='stylesheet' type='text/css' href='postfix-doc.css'>
</head>
<body>
<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix After-Queue Content Filter </h1>
<hr>
<h2>Introduction</h2>
<p> This document requires Postfix version 2.1 or later. </p>
<p> Normally, Postfix receives mail, stores it in the mail queue
and then delivers it. With the external content filter described
here, mail is filtered AFTER it is queued. This approach decouples
mail receiving processes from mail filtering processes, and gives
you maximal control over how many filtering processes you are
willing to run in parallel. </p>
<p> The after-queue content filter is meant to be used as follows: </p>
<blockquote>
<table>
<tr>
<td bgcolor="#f0f0ff" align="center" valign="middle">
Network or<br> local users </td>
<td align="center" valign="middle"> <tt> -> </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
Postfix<br> queue </td>
<td align="center" valign="middle"> <tt> -> </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
<b>Content<br> filter</b> </td>
<td align="center" valign="middle"> <tt> -> </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
Postfix<br> queue </td>
<td align="center" valign="middle"> <tt> -> </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
Network or<br> local mailbox </td>
</tr>
</table>
</blockquote>
<p> This document describes implementations that use a single
Postfix instance for everything: receiving, filtering and delivering
mail. Applications that use two separate Postfix instances will
be covered by a later version of this document. </p>
<p> The after-queue content filter is not to be confused with the
approaches described in the <a href="SMTPD_PROXY_README.html">SMTPD_PROXY_README</a> or <a href="MILTER_README.html">MILTER_README</a>
documents,
where incoming SMTP mail is filtered BEFORE it is stored into the
Postfix queue. </p>
<p> This document describes two approaches to content filter
all email, as well as several options to filter mail selectively: </p>
<ul>
<li><a href="#principles">Principles of operation</a>
<li>Simple content filter
<ul>
<li><a href="#simple_filter">Simple content filter example</a>
<li><a href="#simple_performance">Simple content filter performance</a>
<li><a href="#simple_limitations">Simple content filter limitations</a>
<li><a href="#simple_turnoff">Turning off the simple content filter</a>
</ul>
<li>Advanced content filter
<ul>
<li><a href="#advanced_filter">Advanced content filter example</a>
<li><a href="#performance">Advanced content filter performance</a>
<li><a href="#advanced_turnoff">Turning off the advanced content filter</a>
</ul>
<li>Selective content filtering
<ul>
<li><a href="#remote_only">Filtering mail from outside users only</a>
<li><a href="#domain_dependent">Different filters for different domains</a>
<li><a href="#dynamic_filter">FILTER actions in access or header/body tables</a>
</ul>
</ul>
<h2><a name="principles">Principles of operation</a> </h2>
<p> An after-queue content filter receives unfiltered mail from Postfix
(as described further below) and can do one of the following: </p>
<ol>
<li> <p> Re-inject the mail back into Postfix, perhaps after changing
content and/or destination. </p>
<li> <p> Discard or quarantine the mail. </p>
<li> <p> Reject the mail (by sending a suitable status code back to
Postfix). Postfix will send the mail back to the sender address. </p>
</ol>
<p> NOTE: in this time of mail worms and forged spam, it is a VERY
BAD IDEA to send viruses back to the sender address, because the
sender address is almost certainly not the originator. It is better
to discard known viruses, and to quarantine material that is
suspect so that a human can decide what to do with it. </p>
<h2><a name="simple_filter">Simple content filter example</a></h2>
<p> The first example is simple to set up, but has major limitations
that will be addressed in a second example. Postfix receives
unfiltered mail from the network with the <a href="smtpd.8.html">smtpd(8)</a> server, and
delivers unfiltered mail to a content filter with the Postfix
<a href="pipe.8.html">pipe(8)</a> delivery agent. The content filter injects filtered mail
back into Postfix with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command, so that
Postfix can deliver it to the final destination. </p>
<p> This means that mail submitted via the Postfix <a href="sendmail.1.html">sendmail(1)</a>
command cannot be content filtered. </p>
<p> In the figure below, names followed by a number represent
Postfix commands or daemon programs. See the <a href="OVERVIEW.html">OVERVIEW</a>
document for an introduction to the Postfix architecture. </p>
<blockquote>
<table>
<tr>
<td align="center" valign="top"> Unfiltered<br> <br> </td>
<td align="center" valign="top"> <tt> -></tt><br> <br> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
<a href="smtpd.8.html">smtpd(8)</a><br> <br> <a href="pickup.8.html">pickup(8)</a> </td>
<td align="center" valign="middle"> <tt> >- </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
<a href="cleanup.8.html">cleanup(8)</a> </td>
<td align="center" valign="middle"> <tt> -> </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
<a href="qmgr.8.html">qmgr(8)</a><br> Postfix <br> queue </td>
<td align="center" valign="middle"> <tt> -< </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
<a href="local.8.html">local(8)</a><br> <a href="smtp.8.html">smtp(8)</a><br> <a href="pipe.8.html">pipe(8)</a> </td>
<td align="center" valign="top"> <tt> -></tt><br> <tt>
-></tt><br> </td>
<td align="center" valign="top"> Filtered<br> Filtered<br>
</td>
</tr>
<tr>
<td colspan="2"> </td>
<td align="center" valign="middle"> ^<br> <tt> | </tt> </td>
<td colspan="5"> </td>
<td align="center" valign="middle"> <tt> |<br> v </tt> </td>
<td colspan="2"> </td>
</tr>
<tr>
<td colspan="2"> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
<a href="QSHAPE_README.html#maildrop_queue"> maildrop <br>
queue </a> </td>
<td align="center" valign="middle"> <tt> <- </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">Postfix<br>
<a href="postdrop.1.html">postdrop(1)</a> </td>
<td align="center" valign="middle"> <tt> <- </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">Postfix<br>
<a href="sendmail.1.html">sendmail(1)</a> </td>
<td align="center" valign="middle"> <tt> <- </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">Content
<br> filter </td>
<td colspan="2"> </td>
</tr>
</table>
</blockquote>
<p> The content filter can be a simple shell script like this: </p>
<blockquote>
<pre>
1 #!/bin/sh
2
3 # Simple shell-based filter. It is meant to be invoked as follows:
4 # /path/to/script -f sender recipients...
5
6 # Localize these. The -G option does nothing before Postfix 2.3.
7 INSPECT_DIR=/var/spool/filter
8 SENDMAIL="/usr/sbin/sendmail -G -i" # NEVER NEVER NEVER use "-t" here.
9
10 # Exit codes from <sysexits.h>
11 EX_TEMPFAIL=75
12 EX_UNAVAILABLE=69
13
14 # Clean up when done or when aborting.
15 trap "rm -f in.$$" 0 1 2 3 15
16
17 # Start processing.
18 cd $INSPECT_DIR || {
19 echo $INSPECT_DIR does not exist; exit $EX_TEMPFAIL; }
20
21 cat >in.$$ || {
22 echo Cannot save mail to file; exit $EX_TEMPFAIL; }
23
24 # Specify your content filter here.
25 # filter <in.$$ || {
26 # echo Message content rejected; exit $EX_UNAVAILABLE; }
27
28 $SENDMAIL "$@" <in.$$
29
30 exit $?
</pre>
</blockquote>
<p> Notes: </p>
<ul>
<li> <p> Line 8: The -G option says the filter output is not a local
mail submission: don't do silly things like appending the local
domain name to addresses in message headers. This option does
nothing before Postfix version 2.3. </p>
<li> <p> Line 8: The -i option says don't stop reading input when
a line contains "." only. </p>
<li> <p> Line 8: NEVER NEVER NEVER use the "-t" command-line option
here. It will mis-deliver mail, like sending messages from a mailing
list back to the mailing list. </p>
<li> <p> Line 21: The idea is to first capture the message to
file and then run the content through a third-party content filter
program. </p>
<li> <p> Line 22: If the message cannot be captured to file, mail
delivery is deferred by terminating with exit status 75 (EX_TEMPFAIL).
Postfix places the message in the deferred mail queue and tries
again later. </p>
<li> <p> Line 25: You will need to specify a real content filter
program here that receives the content on standard input. </p>
<li> <p> Line 26: If the content filter program finds a problem,
the mail is bounced by terminating with exit status 69 (EX_UNAVAILABLE).
Postfix will send the message back to the sender as undeliverable
mail.
</p>
<li> <p> NOTE: in this time of mail worms and spam, it is a BAD
IDEA to send known viruses or spam back to the sender, because that
address is likely to be forged. It is safer to discard known viruses
and to quarantine suspicious content so that it can
be inspected by a human being. </p>
<li> <p> Line 28: If the content is OK, it is given as input to
the Postfix sendmail command, and the exit status of the filter
command is whatever exit status the Postfix sendmail command
produces. Postfix will deliver the message as usual. </p>
<li> <p> Line 30: Postfix returns the exit status of the Postfix
sendmail command. </p>
</ul>
<p> I suggest that you first run this script by hand until you are
satisfied with the results. Run it with a real message (headers+body)
as input: </p>
<blockquote>
<pre>
% /path/to/script -f sender -- recipient... <message-file
</pre>
</blockquote>
<p> Once you're satisfied with the content filtering script: </p>
<ul>
<li> <p> Create a dedicated local user account called "filter". This
user handles all potentially dangerous mail content - that is
why it should be a separate account. Do not use "nobody", and
most certainly do not use "root" or "postfix". </p>
<li> <p> Create a directory /var/spool/filter that is accessible only
to the "filter" user. This is where the content filtering script
is supposed to store its temporary files. </p>
<li> <p> Configure Postfix to deliver mail to the content filter
with the <a href="pipe.8.html">pipe(8)</a> delivery agent (see the <a href="pipe.8.html">pipe(8)</a> manpage for a
description of the command syntax below). </p>
<pre>
/etc/postfix/<a href="master.5.html">master.cf</a>:
# =============================================================
# service type private unpriv chroot wakeup maxproc command
# (yes) (yes) (yes) (never) (100)
# =============================================================
filter unix - n n - 10 pipe
flags=Rq user=filter null_sender=
argv=/path/to/script -f ${sender} -- ${recipient}
</pre>
<p> This runs up to 10 content filters in parallel. Instead of a
limit of 10 concurrent processes, use whatever process limit is
feasible for your machine. Content inspection software can gobble
up a lot of system resources, so you don't want to have too much
of it running at the same time. The empty null_sender setting is
required with Postfix 2.3 and later. </p>
<li> <p> To turn on content filtering for mail arriving via SMTP
only, append "-o <a href="postconf.5.html#content_filter">content_filter</a>=filter:dummy" to the <a href="master.5.html">master.cf</a>
entry that defines the Postfix SMTP server: </p>
<pre>
/etc/postfix/<a href="master.5.html">master.cf</a>:
# =============================================================
# service type private unpriv chroot wakeup maxproc command
# (yes) (yes) (yes) (never) (100)
# =============================================================
smtp inet ...other stuff here, do not change... smtpd
-o <a href="postconf.5.html#content_filter">content_filter</a>=filter:dummy
</pre>
<p> The "-o <a href="postconf.5.html#content_filter">content_filter</a>" line causes Postfix to add one content
filter request record to each incoming mail message, with content
"filter:dummy". This record overrides the normal mail routing
and causes mail to be given to the content filter instead. </p>
<p> The <a href="postconf.5.html#content_filter">content_filter</a> configuration parameter expects a value of
the form <i>transport:destination</i>. The <i>transport</i> name
specifies the first field of a mail delivery agent definition in
<a href="master.5.html">master.cf</a>; the syntax of the next-hop <i>destination</i> is described
in the manual page of the corresponding delivery agent. </p>
<p> The meaning of an empty next-hop filter <i>destination</i> is
version dependent. Postfix 2.7 and later will use the recipient
domain; earlier versions will use $<a href="postconf.5.html#myhostname">myhostname</a>. Specify
"<a href="postconf.5.html#default_filter_nexthop">default_filter_nexthop</a> = $<a href="postconf.5.html#myhostname">myhostname</a>" for compatibility with Postfix
2.6 or earlier, or specify a non-empty next-hop filter <i>destination</i>.
</p>
<p> The <a href="postconf.5.html#content_filter">content_filter</a> setting has lower precedence than a FILTER
action that is specified in an <a href="access.5.html">access(5)</a>, <a href="header_checks.5.html">header_checks(5)</a> or
<a href="header_checks.5.html">body_checks(5)</a> table. </p>
<li> <p> Execute "<b>postfix reload</b>" to complete the change.
</p>
</ul>
<h2> <a name="simple_performance">Simple content filter performance</a> </h2>
<p> With the shell script as shown above you will lose a factor of
four in Postfix performance for transit mail that arrives and leaves
via SMTP. You will lose another factor in transit performance for
each additional temporary file that is created and deleted in the
process of content filtering. The performance impact is less for
mail that is submitted or delivered locally, because such deliveries
are already slower than SMTP transit mail. </p>
<h2><a name="simple_limitations">Simple content filter limitations</a></h2>
<p> The problem with content filters like the one above is that
they are not very robust. The reason is that the software does not
talk a well-defined protocol with Postfix. If the filter shell
script aborts because the shell runs into some memory allocation
problem, the script will not produce a nice exit status as defined
in the file /usr/include/sysexits.h. Instead of going to the
<a href="QSHAPE_README.html#deferred_queue">deferred queue</a>, mail will bounce. The same lack of robustness can
happen when the content filtering software itself runs into a
resource problem. </p>
<p> The simple content filter method is not suitable for content
filter actions that are invoked via <a href="postconf.5.html#header_checks">header_checks</a> or <a href="postconf.5.html#body_checks">body_checks</a>
patterns. These patterns will be applied again after mail is
re-injected with the Postfix sendmail command, resulting in a mail
filtering loop. The advanced content filtering method (see below)
makes it possible to turn off <a href="postconf.5.html#header_checks">header_checks</a> or <a href="postconf.5.html#body_checks">body_checks</a> patterns
for filtered mail. </p>
<h2><a name="simple_turnoff">Turning off the simple content filter</a> </h2>
<p> To turn off "simple" content filtering: </p>
<ul> <li> <p> Edit the <a href="master.5.html">master.cf</a> file, remove the "-o
<a href="postconf.5.html#content_filter">content_filter</a>=filter:dummy" text from the entry that defines the
Postfix SMTP server. </p>
<li> <p> Execute "<b>postsuper -r ALL</b>" to remove content
filter request records from existing queue files. </p>
<li> <p> Execute another "<b>postfix reload</b>". </p>
</ul>
<h2><a name="advanced_filter">Advanced content filter example</a></h2>
<p> The second example is more complex, but can give better
performance, and is less likely to bounce mail when the machine
runs into some resource problem. This content filter receives
unfiltered mail with SMTP on localhost port 10025, and sends filtered
mail back into Postfix with SMTP on localhost port 10026. </p>
<p> For non-SMTP capable content filtering software, Bennett Todd's
SMTP proxy implements a nice PERL/SMTP content filtering framework.
See: <a href="https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/">https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/</a>. </p>
<p> In the figure below, names followed by a number represent
Postfix commands or daemon programs. See the <a href="OVERVIEW.html">OVERVIEW</a>
document for an introduction to the Postfix architecture. </p>
<blockquote>
<table>
<tr>
<td align="center" valign="middle"> Unfiltered<br> <br>
Unfiltered</td>
<td align="center" valign="middle"> <tt> -></tt><br> <br>
<tt> -> </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
<a href="smtpd.8.html">smtpd(8)</a><br> <br> <a href="pickup.8.html">pickup(8)</a> </td>
<td align="center" valign="middle"> <tt> >- </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
<a href="cleanup.8.html">cleanup(8)</a> </td>
<td align="center" valign="middle"> <tt> -> </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
<a href="qmgr.8.html">qmgr(8)</a><br> Postfix <br> queue </td>
<td align="center" valign="middle"> <tt> -< </tt> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
<a href="smtp.8.html">smtp(8)</a><br> <br> <a href="local.8.html">local(8)</a> </td>
<td align="center" valign="middle"> <tt> -></tt><br> <br>
<tt> -> </tt></td>
<td align="center" valign="middle"> Filtered<br> <br>
Filtered</td>
</tr>
<tr>
<td colspan="4"> </td>
<td align="center" valign="middle"> ^<br> <tt> | </tt> </td>
<td> </td>
<td align="center" valign="middle"> <tt> |<br> v </tt> </td>
<td colspan="4"> </td>
</tr>
<tr>
<td colspan="4"> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
<a href="smtpd.8.html">smtpd(8)</a><br> 10026 </td>
<td> </td>
<td bgcolor="#f0f0ff" align="center" valign="middle">
<a href="smtp.8.html">smtp(8)</a><br> </td>
<td colspan="4"> </td>
</tr>
<tr>
<td colspan="4"> </td>
<td align="center" valign="middle"> ^<br> <tt> | </tt> </td>
<td> </td>
<td align="center" valign="middle"> <tt> |<br> v </tt> </td>
<td colspan="4"> </td>
</tr>
<tr>
<td colspan="4"> </td>
<td colspan="3" bgcolor="#f0f0ff" align="center"
valign="middle">content filter 10025</td>
<td colspan="4"> </td>
</tr>
</table>
</blockquote>
<p> The example given here filters all mail, including mail that
arrives via SMTP and mail that is locally submitted via the Postfix
sendmail command (local submissions enter Postfix via the <a href="pickup.8.html">pickup(8)</a>
server; to keep the figure simple we omit local submission details).
See examples near the end of this document for
how to exclude local users from filtering, or how to configure a
destination dependent content filter. </p>
<p> You can expect to lose about a factor of two in Postfix
performance for mail that arrives and leaves via SMTP, provided
that the content filter creates no temporary files. Each temporary
file created by the content filter adds another factor to the
performance loss. </p>
<h3>Advanced content filter: requesting that all mail is filtered</h3>
<p> To enable the advanced content filter method for all mail,
specify in <a href="postconf.5.html">main.cf</a>: </p>
<blockquote>
<pre>
/etc/postfix/<a href="postconf.5.html">main.cf</a>:
<a href="postconf.5.html#content_filter">content_filter</a> = scan:localhost:10025
<a href="postconf.5.html#receive_override_options">receive_override_options</a> = <a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
</pre>
</blockquote>
<ul>
<li> <p> The "<a href="postconf.5.html#receive_override_options">receive_override_options</a>" line disables address
manipulation before the content filter, so that the content filter
sees the original mail addresses instead of the result of virtual
alias expansion, canonical mapping, automatic bcc, address
masquerading, etc. </p>
<li> <p> The "<a href="postconf.5.html#content_filter">content_filter</a>" line causes Postfix to add one content
filter request record to each incoming mail message, with content
"scan:localhost:10025". The content filter request records are
added by the <a href="smtpd.8.html">smtpd(8)</a> and <a href="pickup.8.html">pickup(8)</a> servers (and <a href="qmqpd.8.html">qmqpd(8)</a> if you
decide to enable this service). </p>
<li> <p> Content filter requests are stored in queue files; this
is how Postfix keeps track of what mail needs filtering. When a
queue file contains a content filter request, the queue manager
will deliver the mail to the specified content filter regardless
of its final destination. </p>
<li> <p> The <a href="postconf.5.html#content_filter">content_filter</a> configuration parameter expects a value
of the form <i>transport:destination</i>. The <i>transport</i> name
specifies the first field of a mail delivery agent definition in
<a href="master.5.html">master.cf</a>; the syntax of the next-hop <i>destination</i> is described
in the manual page of the corresponding delivery agent. </p>
<li> <p> The meaning of an empty next-hop filter <i>destination</i>
is version dependent. Postfix 2.7 and later will use the recipient
domain; earlier versions will use $<a href="postconf.5.html#myhostname">myhostname</a>. Specify
"<a href="postconf.5.html#default_filter_nexthop">default_filter_nexthop</a> = $<a href="postconf.5.html#myhostname">myhostname</a>" for compatibility with Postfix
2.6 or earlier, or specify a non-empty next-hop filter <i>destination</i>.
<li> <p> The <a href="postconf.5.html#content_filter">content_filter</a> setting has lower precedence than a
FILTER action that is specified in an <a href="access.5.html">access(5)</a>, <a href="header_checks.5.html">header_checks(5)</a>
or <a href="header_checks.5.html">body_checks(5)</a> table. </p>
</ul>
<h3> Advanced content filter: sending unfiltered mail to the content
filter</h3>
<p> In this example, "scan" is an instance of the Postfix SMTP
client with slightly different configuration parameters. This is
how one would set up the service in the Postfix <a href="master.5.html">master.cf</a> file:
</p>
<blockquote>
<pre>
/etc/postfix/<a href="master.5.html">master.cf</a>:
# =============================================================
# service type private unpriv chroot wakeup maxproc command
# (yes) (yes) (yes) (never) (100)
# =============================================================
scan unix - - n - 10 smtp
-o <a href="postconf.5.html#smtp_send_xforward_command">smtp_send_xforward_command</a>=yes
-o <a href="postconf.5.html#disable_mime_output_conversion">disable_mime_output_conversion</a>=yes
-o <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a>=
</pre>
</blockquote>
<ul>
<li> <p> This runs up to 10 content filters in parallel. Instead
of a limit of 10 concurrent processes, use whatever process limit
is feasible for your machine. Content inspection software can
gobble up a lot of system resources, so you don't want to have too
much of it running at the same time. </p>
<li> <p> With "-o <a href="postconf.5.html#smtp_send_xforward_command">smtp_send_xforward_command</a>=yes", the scan transport
will try to forward the original client name and IP address
through the content filter to the
after-filter smtpd process, so that filtered mail is logged with
the real client name IP address. See <a href="smtp.8.html">smtp(8)</a> and <a href="XFORWARD_README.html">XFORWARD_README</a>
for more information. </p>
<li> <p> The "-o <a href="postconf.5.html#disable_mime_output_conversion">disable_mime_output_conversion</a>=yes" is a workaround
that prevents the breaking of domainkeys and other digital signatures.
This is needed because some SMTP-based content filters don't announce
8BITMIME support, even though they can handle 8-bit mail. </p>
<li> <p> The "-o <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a>=" is a workaround that prevents
local address rewriting with <a href="generic.5.html">generic(5)</a> maps. Such rewriting should
happen only when mail is sent out to the Internet. </p>
</ul>
<h3>Advanced content filter: running the content filter</h3>
<p> The content filter can be set up with the Postfix spawn service,
which is the Postfix equivalent of inetd. For example, to instantiate
up to 10 content filtering processes on localhost port 10025: </p>
<blockquote>
<pre>
/etc/postfix/<a href="master.5.html">master.cf</a>:
# ===================================================================
# service type private unpriv chroot wakeup maxproc command
# (yes) (yes) (yes) (never) (100)
# ===================================================================
localhost:10025 inet n n n - 10 spawn
user=filter argv=/path/to/filter localhost 10026
</pre>
</blockquote>
<ul>
<li> <p> "filter" is a dedicated local user account. The user will
never log in, and can be given a "*" password and non-existent
shell and home directory. This user handles all potentially
dangerous mail content - that is why it should be a separate account.
</p>
<li> <p> By default, Postfix will terminate a command that runs
longer than <a href="postconf.5.html#command_time_limit">command_time_limit</a> seconds (default: 1000s). This is a
safety measure that prevents filters from running forever. </p>
</ul>
<p> If you want to have your filter listening on port localhost:10025
instead of Postfix, then you must run your filter as a stand-alone
program, and must not use the Postfix spawn service. </p>
<h3>Advanced filter: injecting mail back into Postfix</h3>
<p> The job of the content filter is to either bounce mail with a
suitable diagnostic, or to feed the mail back into Postfix through
a dedicated listener on port localhost 10026. </p>
<p> The simplest content filter just copies SMTP commands and data
between its inputs and outputs. If it has a problem, all it has to
do is to reply to an input of `.' from Postfix with `550 content
rejected', and to disconnect without sending `.' on the connection
that injects mail back into Postfix. </p>
<blockquote>
<pre>
/etc/postfix/<a href="master.5.html">master.cf</a>:
# ===================================================================
# service type private unpriv chroot wakeup maxproc command
# (yes) (yes) (yes) (never) (100)
# ===================================================================
localhost:10026 inet n - n - 10 smtpd
-o <a href="postconf.5.html#content_filter">content_filter</a>=
-o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_unknown_recipient_checks">no_unknown_recipient_checks</a>,<a href="postconf.5.html#no_header_body_checks">no_header_body_checks</a>,<a href="postconf.5.html#no_milters">no_milters</a>
-o <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a>=
-o <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>=
-o <a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a>=
# Postfix 2.10 and later: specify empty <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>.
-o <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>=
-o <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>=<a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,reject
-o <a href="postconf.5.html#mynetworks">mynetworks</a>=127.0.0.0/8
-o <a href="postconf.5.html#smtpd_authorized_xforward_hosts">smtpd_authorized_xforward_hosts</a>=127.0.0.0/8
</pre>
</blockquote>
<ul>
<li> <p> NOTE: do not use spaces around the "=" or "," characters. </p>
<li> <p> NOTE: the SMTP server must not have a smaller process
limit than the "filter" <a href="master.5.html">master.cf</a> entry. </p>
<li> <p> The "-o <a href="postconf.5.html#content_filter">content_filter</a>=" overrides <a href="postconf.5.html">main.cf</a> settings, and
requests no content filtering for mail from the content filter.
This is required or else mail will loop. </p>
<li> <p> The "-o <a href="postconf.5.html#receive_override_options">receive_override_options</a>" overrides <a href="postconf.5.html">main.cf</a> settings
to avoid duplicating work that was already done before the content
filter. These options are complementary to the options that are
specified in <a href="postconf.5.html">main.cf</a>: </p>
<ul>
<li> <p> We specify "<a href="postconf.5.html#no_unknown_recipient_checks">no_unknown_recipient_checks</a>" to disable
attempts to find out if a recipient is unknown. </p>
<li> <p> We specify "<a href="postconf.5.html#no_header_body_checks">no_header_body_checks</a>" to disable header/body
checks. </p>
<li> <p> We specify "<a href="postconf.5.html#no_milters">no_milters</a>" to disable Milter applications
(this option is available only in Postfix 2.3 and later). </p>
<li> <p> We don't specify "<a href="postconf.5.html#no_address_mappings">no_address_mappings</a>" here. This
enables virtual alias expansion, canonical mappings, address
masquerading, and other address mappings after the content
filter. The <a href="postconf.5.html">main.cf</a> setting of "<a href="postconf.5.html#receive_override_options">receive_override_options</a>"
disables these mappings before the content filter. </p>
</ul>
<p> These receive override options are either implemented by the
SMTP server itself, or they are passed on to the cleanup server.
</p>
<li> <p> The "-o smtpd_xxx_restrictions" and "-o <a href="postconf.5.html#mynetworks">mynetworks</a>=127.0.0.0/8"
override <a href="postconf.5.html">main.cf</a> settings. They turn off junk mail controls that
would only waste time here.
<li> <p> With "-o <a href="postconf.5.html#smtpd_authorized_xforward_hosts">smtpd_authorized_xforward_hosts</a>=127.0.0.0/8",
the scan transport will try to forward the original client name
and IP address to the after-filter smtpd process, so that filtered
mail is logged with the real client name and IP address. See
<a href="XFORWARD_README.html">XFORWARD_README</a> and <a href="smtpd.8.html">smtpd(8)</a>. </p>
</ul>
<h2><a name="performance">Advanced content filter performance</a></h2>
<p> With the "sandwich" approach to content filtering described
here, it is important to match the filter concurrency to the
available CPU, memory and I/O resources. Too few content filter
processes and mail accumulates in the <a href="QSHAPE_README.html#active_queue">active queue</a> even with low
traffic volume; too much concurrency and Postfix ends up deferring
mail destined for the content filter because processes fail due to
insufficient resources. </p>
<p> Currently, content filter performance tuning is a process of
trial and error; analysis is handicapped because filtered and
unfiltered messages share the same queue. As mentioned in the
introduction of this document, content filtering with multiple
Postfix instances will be covered in a future version. </p>
<h2><a name="advanced_turnoff">Turning off the advanced content filter</a> </h2>
<p> To turn off "advanced" content filtering: </p>
<ul> <li> <p> Delete or comment out the two following <a href="postconf.5.html">main.cf</a> lines.
The other changes made for advanced content filtering have no effect
when content filtering is turned off. </p>
<blockquote>
<pre>
/etc/postfix/<a href="postconf.5.html">main.cf</a>:
<a href="postconf.5.html#content_filter">content_filter</a> = scan:localhost:10025
<a href="postconf.5.html#receive_override_options">receive_override_options</a> = <a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
</pre>
</blockquote>
<li> <p> Execute "<b>postsuper -r ALL</b>" to remove content
filter request records from existing queue files. </p>
<li> <p> Execute another "<b>postfix reload</b>". </p>
</ul>
<h2><a name="remote_only">Filtering mail from outside users only</a></h2>
<p> The easiest approach is to configure ONE Postfix instance with
multiple SMTP server IP addresses in <a href="master.5.html">master.cf</a>: </p>
<ul>
<li> <p> Two SMTP server IP addresses for mail from inside users only,
with content filtering turned off. </p>
<pre>
/etc/postfix.<a href="master.5.html">master.cf</a>:
# ==================================================================
# service type private unpriv chroot wakeup maxproc command
# (yes) (yes) (yes) (never) (100)
# ==================================================================
1.2.3.4:smtp inet n - n - - smtpd
-o <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>=<a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,reject
127.0.0.1:smtp inet n - n - - smtpd
-o <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>=<a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,reject
</pre>
<li> <p> One SMTP server address for mail from outside users with
content filtering turned on. </p>
<pre>
/etc/postfix.<a href="master.5.html">master.cf</a>:
# =================================================================
# service type private unpriv chroot wakeup maxproc command
# (yes) (yes) (yes) (never) (100)
# =================================================================
1.2.3.5:smtp inet n - n - - smtpd
-o <a href="postconf.5.html#content_filter">content_filter</a>=filter-service:filter-destination
-o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
</pre>
</ul>
<p> After this, you can follow the same procedure as outlined in
the "advanced" or "simple" content filtering examples above, except
that you must not specify "<a href="postconf.5.html#content_filter">content_filter</a>" or "<a href="postconf.5.html#receive_override_options">receive_override_options</a>"
in the <a href="postconf.5.html">main.cf</a> file. </p>
<h2><a name="domain_dependent">Different filters for different
domains</a></h2>
<p> If you are an MX service provider and want to apply different
content filters for different domains, you can configure ONE Postfix
instance with multiple SMTP server IP addresses in <a href="master.5.html">master.cf</a>. Each
address provides a different content filter service. </p>
<blockquote>
<pre>
/etc/postfix.<a href="master.5.html">master.cf</a>:
# =================================================================
# service type private unpriv chroot wakeup maxproc command
# (yes) (yes) (yes) (never) (100)
# =================================================================
# SMTP service for domains that are filtered with service1:dest1
1.2.3.4:smtp inet n - n - - smtpd
-o <a href="postconf.5.html#content_filter">content_filter</a>=service1:dest1
-o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
# SMTP service for domains that are filtered with service2:dest2
1.2.3.5:smtp inet n - n - - smtpd
-o <a href="postconf.5.html#content_filter">content_filter</a>=service2:dest2
-o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
</pre>
</blockquote>
<p> After this, you can follow the same procedure as outlined in
the "advanced" or "simple" content filtering examples above, except
that you must not specify "<a href="postconf.5.html#content_filter">content_filter</a>" or "<a href="postconf.5.html#receive_override_options">receive_override_options</a>"
in the <a href="postconf.5.html">main.cf</a> file. </p>
<p> Set up MX records in the DNS that route each domain to the
proper SMTP server instance. </p>
<h2><a name="dynamic_filter">FILTER actions in access or header/body
tables</a></h2>
<p> The above filtering configurations are static. Mail that follows
a given path is either always filtered or it is never filtered. As
of Postfix 2.0 you can also turn on content filtering on the fly.
</p>
<p> To turn on content filtering with an <a href="access.5.html">access(5)</a> table rule: </p>
<blockquote>
<pre>
/etc/postfix/access:
<i>whatever</i> FILTER foo:bar
</pre>
</blockquote>
<p> To turn on content filtering with a <a href="header_checks.5.html">header_checks(5)</a> or
<a href="header_checks.5.html">body_checks(5)</a> table pattern: </p>
<blockquote>
<pre>
/etc/postfix/header_checks:
/<i>whatever</i>/ FILTER foo:bar
</pre>
</blockquote>
<p> You can do this in smtpd access maps as well as the cleanup
server's header/body_checks. This feature must be used with great
care: you must disable all the UCE features in the after-filter
smtpd and cleanup daemons or else you will have a content filtering
loop. </p>
<p> Limitations: </p>
<ul>
<li> <p> FILTER actions from smtpd access maps and header/body_checks
take precedence over filters specified with the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#content_filter">content_filter</a>
parameter. </p>
<li> <p> If a message triggers more than one filter action, only
the last one takes effect. </p>
<li> <p> The same content filter is applied to all the recipients
of a given message. </p>
</ul>
</body>
</html>
|