madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

properly anchor Content-class match
[etc/mailfilter.git] / procmail / spamfilter
1 #!/usr/bin/procmail
2
3 #TODO: rewrite to use SPAM variable, and do not autotrain spam here, only ham
4
5 PMDIR=${PMDIR:-$HOME/.etc/mailfilter/procmail}
6
7 :0
8 * !PMVAR ?? .
9 {
10   # PMVAR is not defined, so we are being called as filter
11   # thus source the standard defines
12   INCLUDERC=$PMDIR/defines
13   # prevent feeding back to procmail and delete the leading From line
14   PROCMAIL='/bin/cat'
15   # and tell the fucking procmail piece-of-shit to continue to be a filter
16   DEFAULT='|$PROCMAIL'
17 }
18
19 #VERBOSE=yes
20
21 # no need to reprocess messages that went into a spamtrap
22 # UPDATE: retrain them only if diagnosed as non-spam, see below
23 # Note: add E flag to next recipe when uncommenting
24 #:0
25 #* SPAMTRAPPED ?? .
26 #{
27 #  LOG="spamfilter:  skipping checks for spamtrapped message$NL"
28 #  :0 fw
29 #  |$FORMAIL -I"X-Spam: spamtrapped"
30 #}
31
32 # check whether this message is being resubmitted
33 :0
34 *$ $MSG_DEJAVU
35 {
36   :0
37   * TRAINED_AS ?? .
38   {
39     LOG="spamfilter:  skipping already trained $TRAINED_AS$NL"
40     :0 fw
41     |$FORMAIL -I"X-Spam: $TRAINED_AS (already trained)"
42     :0
43     * TRAINED_AS ?? spam
44     { IS_SPAM=already-trained }
45   }
46
47   :0 E
48   {
49     LOG="spamfilter:  skipping resubmitted message$NL"
50     :0 fw
51     |$FORMAIL -I"X-Spam: unknown (resubmitted)"
52   }
53 }
54
55 # do not run spamfilters if the message destination is already set
56 :0 E
57 * DEST ?? .
58 {
59   LOG="spamfilter:  message already routed to '$DEST'$NL"
60   :0 fw
61   |$FORMAIL -I"X-Spam: unknown (already routed)"
62   SPAM_UNKNOWN=already-destined
63 }
64
65 # let earlier parts of the mailfilter cause bypassing the checks
66 :0 E
67 * SKIP_SPAMCHECKS ?? .
68 {
69   LOG="spamfilter:  skipping checks as requested: $SKIP_SPAMCHECKS$NL"
70   :0 fw
71   |$FORMAIL -I"X-Spam: unknown (skip requested)"
72   SPAM_UNKNOWN=skip-requested
73 }
74
75 # honour skip-spamchecks to exclude certain messages from spam checks
76 # altogether
77 :0 EBH
78 * ? $EGREP -qif $CONF/skip-spamchecks
79 {
80   LOG="spamfilter:  skipping checks as per skip-spamchecks$NL"
81   :0 fw
82   |$FORMAIL -I"X-Spam: unknown (check skipped)"
83   SPAM_UNKNOWN=skip-match
84   SKIP_SPAMCHECKS=match
85 }
86
87 # sanity check on message size
88 :0 E
89 * > $SPAMCHECK_MAX_MESSAGE_SIZE
90 {
91   LOG="spamfilter:  skipping check because message size exceeds $SPAMCHECK_MAX_MESSAGE_SIZE bytes$NL"
92   :0 fw
93   |$FORMAIL -I"X-Spam: unknown (message larger than $SPAMCHECK_MAX_MESSAGE_SIZE bytes)"
94   SPAM_UNKNOWN=too-large
95 }
96
97 # now run the spamfilters
98 :0 E
99 {
100   INCLUDERC=$PMDIR/spamtraps
101   INCLUDERC=$PMDIR/spammers
102   INCLUDERC=$PMDIR/spampat
103   INCLUDERC=$PMDIR/pre-spam-cleanup
104
105   # crm114
106   CRM_SPAM=UNKNOWN
107   CRM_SCORE=0
108   :0
109   * !SKIP_CRM ?? .
110   {
111     #TODO: somehow filter out headers we added
112     :0 fw
113     |$CRM114
114
115     :0
116     * ^X-CRM114-Status: \/[A-Z]+
117     { CRM_SPAM=$MATCH }
118
119     :0
120     * ^X-CRM114-Status: .+\([ ]*\/-?[.0-9]+
121     { CRM_SCORE=$MATCH }
122
123     LOG="crm114:      $CRM_SPAM/$CRM_SCORE$NL"
124   }
125
126   # spamassassin
127   SA_STATUS=Unknown
128   SA_SCORE=0
129   SA_TESTS=none
130   :0
131   * !SKIP_SA ?? .
132   {
133     :0 fw
134     |$SPAMC
135
136     :0
137     * ^X-Spam-Status: \/[A-Za-z]+
138     { SA_SPAM=$MATCH }
139
140     :0
141     * ^X-Spam-Status: .+score=\/-?[.0-9]+
142     { SA_SCORE=$MATCH }
143
144     :0
145     * ^X-Spam-Status: .+tests=\/[^ ]+
146     { SA_TESTS=$MATCH }
147
148     LOG="SA:          $SA_SPAM/$SA_SCORE/$SA_TESTS$NL"
149   }
150
151   ## CASE 0: crm114 is unsure/untrained
152   :0
153   * CRM_SPAM ?? UNSURE
154   {
155     # retrain spamtrapped message
156     :0
157     * SPAMTRAPPED ?? .
158     {
159       LOG="spamfilter:  scheduling retraining with SPAM due to spamtrap$NL"
160       :0 fw
161       |$FORMAIL -A "X-CRM114-Autotrain: spam, due to spamtrap"
162       RETRAIN=spam
163     }
164
165     # retrain as ham
166     :0 E
167     * ? perl -e "$SA_SCORE <= $CRM_UNSURE_SA_AUTOTRAIN_LIMIT_HAM || exit 1"
168     {
169       LOG="spamfilter:  scheduling retraining with HAM (score $SA_SCORE <= $CRM_UNSURE_SA_AUTOTRAIN_LIMIT_HAM)$NL"
170       :0 fw
171       |$FORMAIL -A "X-CRM114-Autotrain: ham, according to SA (score $SA_SCORE <= $CRM_UNSURE_SA_AUTOTRAIN_LIMIT_HAM)"
172       RETRAIN=ham
173     }
174
175     # retrain as spam
176     :0 E
177     * 1^0 ? perl -e "$SA_SCORE > $CRM_UNSURE_SA_AUTOTRAIN_LIMIT_SPAM || exit 1"
178     {
179       LOG="spamfilter:  scheduling retraining with SPAM (score $SA_SCORE > $CRM_UNSURE_SA_AUTOTRAIN_LIMIT_SPAM)$NL"
180       :0 fw
181       |$FORMAIL -A "X-CRM114-Autotrain: spam, according to SA (score $SA_SCORE > $CRM_UNSURE_SA_AUTOTRAIN_LIMIT_SPAM)"
182       RETRAIN=spam
183     }
184
185     # skip retraining if SA is not convinced
186     :0 E
187     {
188       LOG="spamfilter:  will not autotrain crm114 because SA is not convinced ($CRM_UNSURE_SA_AUTOTRAIN_LIMIT_HAM <= $SA_SCORE < $CRM_UNSURE_SA_AUTOTRAIN_LIMIT_SPAM)$NL"
189       :0 fw
190       |$FORMAIL -A "X-CRM114-Autotrain: SA is unsure ($CRM_UNSURE_SA_AUTOTRAIN_LIMIT_HAM <= $SA_SCORE < $CRM_UNSURE_SA_AUTOTRAIN_LIMIT_SPAM)"
191       SPAM_UNSURE=sa-unsure
192     }
193   }
194
195   ## CASE 1: disagreement, SA sees ham
196   :0 E
197   * CRM_SPAM ?? SPAM
198   * SA_SPAM ?? No
199   {
200     # message was spamtrapped anyway
201     :0
202     * SPAMTRAPPED ?? .
203     {
204       LOG="spamfilter:  resolving crm114/SA disagreement due to spamtrap ($CRM_SCORE/$SA_SCORE)$NL"
205       RETRAIN=spam
206       :0 fw
207       |$FORMAIL -A "X-CRM114-Retrain: spam, due to spamtrap"
208     }
209
210     # SA is convincing, so retrain crm114
211     :0 E
212     * ? perl -e "$SA_SCORE <= $CRM_MISCLASSIFY_SA_AUTOTRAIN_LIMIT_HAM || exit 1"
213     {
214       LOG="spamfilter:  crm114 found spam ($CRM_SCORE), but SA is more convincing ($SA_SCORE <= $CRM_MISCLASSIFY_SA_AUTOTRAIN_LIMIT_HAM)$NL"
215       RETRAIN=ham
216       :0 fw
217       |$FORMAIL -A "X-CRM114-Retrain: ham, according to SA (score $SA_SCORE <= $CRM_MISCLASSIFY_SA_AUTOTRAIN_LIMIT_HAM)"
218     }
219
220     # SA is not convincing, mark as disagreement
221     :0 E
222     {
223       LOG="spamfilter:  crm114 found spam ($CRM_SCORE), but SA thinks it's ham ($SA_SCORE)$NL"
224       SPAM_DISAGREE=sa-ham
225       :0 fw
226       |$FORMAIL -I "X-Spam: disagree (crm114:spam/$CRM_SCORE SA:ham/$SA_SCORE)"
227     }
228   }
229
230   ## CASE 1: disagreement, SA sees spam
231   :0 E
232   * CRM_SPAM ?? GOOD
233   * SA_SPAM ?? Yes
234   {
235     # message was spamtrapped anyway
236     :0
237     * SPAMTRAPPED ?? .
238     {
239       LOG="spamfilter:  resolving crm114/SA disagreement due to spamtrap ($CRM_SCORE/$SA_SCORE)$NL"
240       RETRAIN=spam
241       :0 fw
242       |$FORMAIL -A "X-CRM114-Retrain: spam, due to spamtrap"
243     }
244
245     # SA is convincing, so retrain crm114
246     :0
247     * ? perl -e "$SA_SCORE > $CRM_MISCLASSIFY_SA_AUTOTRAIN_LIMIT_SPAM || exit 1"
248     {
249       LOG="spamfilter:  crm114 found ham ($CRM_SCORE), but SA is more convincing ($SA_SCORE > $CRM_MISCLASSIFY_SA_AUTOTRAIN_LIMIT_SPAM)$NL"
250       RETRAIN=spam
251       :0 fw
252       |$FORMAIL -A "X-CRM114-Retrain: spam, according to SA (score $SA_SCORE > $CRM_MISCLASSIFY_SA_AUTOTRAIN_LIMIT_SPAM)"
253     }
254
255     # SA is not convincing, mark as disagreement
256     :0 E
257     {
258       LOG="spamfilter:  crm114 found ham ($CRM_SCORE), but SA thinks it's spam ($SA_SCORE)$NL"
259       SPAM_DISAGREE=sa-spam
260       :0 fw
261       |$FORMAIL -I "X-Spam: disagree (crm114:ham/$CRM_SCORE SA:spam/$SA_SCORE)"
262     }
263   }
264
265   :0 E
266   * CRM_SPAM ?? SPAM
267   * SA_SPAM ?? Yes
268   {
269     IS_SPAM=sa+crm
270     :0 fw
271     |$FORMAIL -I"X-Spam: yes (crm114:$CRM_SCORE SA:$SA_SCORE)"
272   }
273
274   :0 Efw
275   |$FORMAIL -I"X-Spam: no (crm114:$CRM_SCORE SA:$SA_SCORE)"
276 }
277
278 # schedule spamtrapped ham for retraining as spam
279 :0
280 * SPAMTRAPPED ?? .
281 * ! SKIP_SPAMCHECKS ?? .
282 * ! IS_SPAM ?? .
283 {
284   LOG="spamfilter:  found spamtrapped ham, retraining...$NL"
285   :0 fw
286   |$FORMAIL -I"X-Spam: spamtrapped ham"
287   IS_SPAM=spamtrapped-ham
288   RETRAIN=spam
289   SPAM_UNSURE
290 }
291
292 #VERBOSE=no