Commit | Line | Data |
---|---|---|
185b5456 MW |
1 | ### -*-m4-*- |
2 | ### | |
3 | ### Spam filtering for distorted.org.uk Exim configuration | |
4 | ### | |
5 | ### (c) 2012 Mark Wooding | |
6 | ### | |
7 | ||
8 | ###----- Licensing notice --------------------------------------------------- | |
9 | ### | |
10 | ### This program is free software; you can redistribute it and/or modify | |
11 | ### it under the terms of the GNU General Public License as published by | |
12 | ### the Free Software Foundation; either version 2 of the License, or | |
13 | ### (at your option) any later version. | |
14 | ### | |
15 | ### This program is distributed in the hope that it will be useful, | |
16 | ### but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | ### GNU General Public License for more details. | |
19 | ### | |
20 | ### You should have received a copy of the GNU General Public License | |
21 | ### along with this program; if not, write to the Free Software Foundation, | |
22 | ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
23 | ||
24 | DIVERT(null) | |
25 | ###-------------------------------------------------------------------------- | |
26 | ### Spam filtering. | |
27 | ||
28 | SECTION(global, policy)m4_dnl | |
29 | spamd_address = CONF_spamd_address CONF_spamd_port | |
30 | ||
31 | SECTION(routers, allspam)m4_dnl | |
32 | ## If we're verifying an address and the recipient has a `~/.mail/spam-limit' | |
33 | ## file, then look up the recipient and sender addresses to find a plausible | |
34 | ## limit and insert it into the `address_data' where the RCPT ACL can find | |
35 | ## it. This router always declines, so it doesn't affect the overall outcome | |
36 | ## of the verification. | |
37 | fetch_spam_limit: | |
38 | driver = redirect | |
39 | data = :unknown: | |
40 | verify_only = true | |
41 | local_part_suffix = CONF_user_suffix_list | |
42 | local_part_suffix_optional = true | |
43 | check_local_user | |
44 | address_data = \ | |
45 | ${if def:address_data {$address_data}{}} \ | |
46 | ${if exists {CONF_userconf_dir/spam-limit} \ | |
47 | {${lookup {$local_part_prefix\ | |
48 | $local_part\ | |
49 | $local_part_suffix\ | |
50 | @$domain/\ | |
51 | $sender_address} \ | |
52 | nwildlsearch {CONF_userconf_dir/spam-limit} \ | |
b84f89e0 | 53 | {${if match{$value}{\N^-?[0-9]+$\N} \ |
57e46ac8 MW |
54 | {spam_limit=$value} \ |
55 | {}}} \ | |
185b5456 MW |
56 | {}}} \ |
57 | {}} \ | |
58 | ${if exists {CONF_userconf_dir/spam-limit.userv} \ | |
59 | {${run {timeout 5s -- \ | |
60 | userv $local_part exim-spam-limit \ | |
61 | $sender_address \ | |
62 | $local_part_prefix \ | |
63 | $local_part \ | |
64 | $local_part_suffix \ | |
65 | @$domain} \ | |
b84f89e0 | 66 | {${if match{$value}{\N^-?[0-9]+$\N} \ |
185b5456 MW |
67 | {spam_limit=$value} \ |
68 | {}}} \ | |
69 | {}}} \ | |
70 | {}} | |
71 | ||
72 | SECTION(acl, rcpt-hooks)m4_dnl | |
73 | ## Do per-recipient spam-filter processing. | |
74 | require acl = rcpt_spam | |
75 | ||
76 | SECTION(acl, misc)m4_dnl | |
b8b0f13c | 77 | skip_spam_check: |
185b5456 | 78 | |
b8b0f13c MW |
79 | ## If the client is trusted, or this is a new submission, don't |
80 | ## bother with any of this. We will have verified the sender | |
81 | ## fairly aggressively before granting this level of trust. | |
185b5456 | 82 | accept hosts = +trusted |
b8b0f13c MW |
83 | accept condition = ${if eq{$acl_c_mode}{submission}} |
84 | ||
85 | ## Otherwise we should check. | |
86 | deny | |
87 | ||
88 | rcpt_spam: | |
89 | ||
90 | ## See if we should do this check. | |
91 | accept acl = skip_spam_check | |
185b5456 | 92 | |
aa935c91 MW |
93 | ## Always accept mail to `postmaster'. Currently this is not |
94 | ## negotiable; maybe a tweak can be added to `domains.conf' if | |
95 | ## necessary. | |
96 | accept local_parts = postmaster | |
97 | ||
185b5456 MW |
98 | ## Collect the user's spam threshold from the `address_data' |
99 | ## variable, where it was left by the `fetch_spam_limit' router | |
100 | ## during recipient verification. (This just saves duplicating this | |
101 | ## enormous expression.) | |
102 | warn set acl_m_this_spam_limit = \ | |
103 | ${sg {${extract {spam_limit} \ | |
104 | {${if def:address_data \ | |
105 | {$address_data}{}}} \ | |
106 | {$value}{nil}}} \ | |
107 | {^(|.*\\D.*)\$}{CONF_spam_max}} | |
108 | ||
109 | ## If there's a spam limit already established, and it's different | |
110 | ## from this user's limit, then the sender will have to try this user | |
111 | ## again later. | |
112 | defer !hosts = +trusted | |
113 | message = "You'd better try this one later" | |
114 | condition = ${if def:acl_m_spam_limit {true}{false}} | |
115 | condition = ${if ={$acl_m_spam_limit} \ | |
116 | {$acl_m_this_spam_limit} \ | |
117 | {false}{true}} | |
118 | ||
119 | ## There's no limit set yet, or the user's limit is the same as the | |
120 | ## existing one, or the client's local and we're not checking for | |
121 | ## spam anyway. Whichever way, it's safe to set it now. | |
122 | warn set acl_m_spam_limit = $acl_m_this_spam_limit | |
123 | ||
124 | ## All done. | |
125 | accept | |
126 | ||
127 | SECTION(acl, data-spam)m4_dnl | |
128 | ## Do spam checking. | |
129 | require acl = data_spam | |
130 | ||
131 | SECTION(acl, misc)m4_dnl | |
132 | data_spam: | |
133 | ||
b8b0f13c MW |
134 | ## See if we should do this check. |
135 | accept acl = skip_spam_check | |
185b5456 | 136 | |
09ca3919 MW |
137 | ## Check header validity. |
138 | require verify = header_syntax | |
139 | ||
185b5456 MW |
140 | ## Check the message for spam, comparing to the configured limit. |
141 | deny spam = exim:true | |
142 | message = Tinned meat product detected ($spam_score) | |
143 | condition = ${if >{$spam_score_int}{$acl_m_spam_limit} \ | |
144 | {true}{false}} | |
145 | ||
146 | ## Insert headers from the spam check now that we've decided to | |
147 | ## accept the message. | |
148 | warn | |
a882a548 | 149 | |
185b5456 MW |
150 | ## Convert the limit (currently 10x fixed point) into a |
151 | ## decimal for presentation. | |
152 | set acl_m_spam_limit_presentation = \ | |
153 | ${sg{$acl_m_spam_limit}{\N(\d)$\N}{.\$1}} | |
154 | ||
155 | ## Convert the report into something less obnoxious. Plain | |
156 | ## old SpamAssassin has an `X-Spam-Status' header which | |
157 | ## lists the matched rules and provides some other basic | |
158 | ## information. Try to extract something similar from the | |
159 | ## report. | |
160 | ## | |
161 | ## This is rather fiddly. | |
162 | ||
163 | ## Firstly, escape angle brackets, because we'll be using | |
164 | ## them for our own purposes. | |
165 | set acl_m_spam_tests = ${sg{$spam_report}{([!<>])}{!\$1}} | |
166 | ||
167 | ## Trim off the blurb paragraph and the preview. The rest | |
168 | ## should be fairly well behaved. Wrap double angle- | |
169 | ## brackets around the remainder; these can't appear in the | |
170 | ## body because we escaped them all earlier. | |
171 | set acl_m_spam_tests = \ | |
172 | ${sg{$acl_m_spam_tests} \ | |
173 | {\N^(?s).*\n Content analysis details:(.*)$\N} \ | |
174 | {<<\$1>>}} | |
175 | ||
176 | ## Extract the information about the matching rules and | |
177 | ## their scores. Leave `<<...>>' around everything else. | |
178 | set acl_m_spam_tests = \ | |
179 | ${sg{$acl_m_spam_tests} \ | |
4ff4d304 | 180 | {\N(?s)\n\s*(-?[\d.]+)\s+([-\w]+)\s\N} \ |
185b5456 MW |
181 | {>>\$2:\$1,<<}} |
182 | ||
183 | ## Strip everything still in `<<...>>' pairs, including any | |
184 | ## escaped characters inside. | |
185 | set acl_m_spam_tests = \ | |
186 | ${sg{$acl_m_spam_tests}{\N(?s)<<([^!>]+|!.)*>>\N}{}} | |
187 | ||
188 | ## Trim off a trailing comma. | |
189 | set acl_m_spam_tests = ${sg{$acl_m_spam_tests}{,\s*\$}{}} | |
190 | ||
191 | ## Undo the escaping. | |
192 | set acl_m_spam_tests = ${sg{$acl_m_spam_tests}{!(.)}{\$1}} | |
193 | ||
194 | ## Insert the headers. | |
195 | add_header = X-SpamAssassin-Score: \ | |
196 | $spam_score/$acl_m_spam_limit_presentation \ | |
197 | ($spam_bar) | |
198 | add_header = X-SpamAssassin-Status: \ | |
199 | score=$spam_score, \ | |
200 | limit=$acl_m_spam_limit_presentation, \n\t\ | |
201 | tests=$acl_m_spam_tests | |
202 | ||
185b5456 MW |
203 | ## We're good. |
204 | accept | |
205 | ||
206 | DIVERT(null) | |
207 | ###----- That's all, folks -------------------------------------------------- |