Don't quote lambda expressions with `quote'.
[bpt/emacs.git] / lisp / mh-e / mh-junk.el
CommitLineData
dda00b2c 1;;; mh-junk.el --- MH-E interface to anti-spam measures
924df208 2
95df8112 3;; Copyright (C) 2003-2011 Free Software Foundation, Inc.
924df208
BW
4
5;; Author: Satyaki Das <satyaki@theforce.stanford.edu>,
6;; Bill Wohler <wohler@newt.com>
7;; Maintainer: Bill Wohler <wohler@newt.com>
8;; Keywords: mail, spam
9
10;; This file is part of GNU Emacs.
11
5e809f55 12;; GNU Emacs is free software: you can redistribute it and/or modify
924df208 13;; it under the terms of the GNU General Public License as published by
5e809f55
GM
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
924df208
BW
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
5e809f55 23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
924df208
BW
24
25;;; Commentary:
26
27;; Spam handling in MH-E.
28
29;;; Change Log:
30
31;;; Code:
32
33(require 'mh-e)
dda00b2c
BW
34(require 'mh-scan)
35(mh-require-cl)
924df208 36
924df208 37;;;###mh-autoload
a66894d8
BW
38(defun mh-junk-blacklist (range)
39 "Blacklist RANGE as spam.
40
2dcf34f9
BW
41This command trains the spam program in use (see the option
42`mh-junk-program') with the content of RANGE and then handles the
43message(s) as specified by the option `mh-junk-disposition'.
2be362c2 44
2dcf34f9
BW
45Check the documentation of `mh-interactive-range' to see how RANGE is
46read in interactive use.
924df208 47
2dcf34f9
BW
48For more information about using your particular spam fighting
49program, see:
924df208 50
f0d73c14 51 - `mh-spamassassin-blacklist'
924df208 52 - `mh-bogofilter-blacklist'
f0d73c14 53 - `mh-spamprobe-blacklist'"
a66894d8 54 (interactive (list (mh-interactive-range "Blacklist")))
924df208
BW
55 (let ((blacklist-func (nth 1 (assoc mh-junk-choice mh-junk-function-alist))))
56 (unless blacklist-func
57 (error "Customize `mh-junk-program' appropriately"))
f0d73c14
BW
58 (let ((dest (cond ((null mh-junk-disposition) nil)
59 ((equal mh-junk-disposition "") "+")
60 ((eq (aref mh-junk-disposition 0) ?+)
61 mh-junk-disposition)
62 ((eq (aref mh-junk-disposition 0) ?@)
924df208 63 (concat mh-current-folder "/"
f0d73c14
BW
64 (substring mh-junk-disposition 1)))
65 (t (concat "+" mh-junk-disposition)))))
a66894d8 66 (mh-iterate-on-range msg range
47570699 67 (message "Blacklisting message %d..." msg)
924df208 68 (funcall (symbol-function blacklist-func) msg)
47570699 69 (message "Blacklisting message %d...done" msg)
f0d73c14
BW
70 (if (not (memq msg mh-seen-list))
71 (setq mh-seen-list (cons msg mh-seen-list)))
924df208
BW
72 (if dest
73 (mh-refile-a-msg nil (intern dest))
74 (mh-delete-a-msg nil)))
75 (mh-next-msg))))
76
77;;;###mh-autoload
a66894d8 78(defun mh-junk-whitelist (range)
f0d73c14 79 "Whitelist RANGE as ham.
924df208 80
2dcf34f9
BW
81This command reclassifies the RANGE as ham if it were incorrectly
82classified as spam (see the option `mh-junk-program'). It then
83refiles the message into the \"+inbox\" folder.
924df208 84
2dcf34f9
BW
85Check the documentation of `mh-interactive-range' to see how
86RANGE is read in interactive use."
a66894d8 87 (interactive (list (mh-interactive-range "Whitelist")))
924df208
BW
88 (let ((whitelist-func (nth 2 (assoc mh-junk-choice mh-junk-function-alist))))
89 (unless whitelist-func
90 (error "Customize `mh-junk-program' appropriately"))
a66894d8 91 (mh-iterate-on-range msg range
47570699 92 (message "Whitelisting message %d..." msg)
924df208 93 (funcall (symbol-function whitelist-func) msg)
47570699 94 (message "Whitelisting message %d...done" msg)
924df208
BW
95 (mh-refile-a-msg nil (intern mh-inbox)))
96 (mh-next-msg)))
97
98\f
99
f0d73c14 100;; Spamassassin Interface
924df208 101
f0d73c14
BW
102(defvar mh-spamassassin-executable (executable-find "spamassassin"))
103(defvar mh-sa-learn-executable (executable-find "sa-learn"))
924df208 104
dda00b2c 105;;;###mh-autoload
f0d73c14
BW
106(defun mh-spamassassin-blacklist (msg)
107 "Blacklist MSG with SpamAssassin.
924df208 108
af435184 109SpamAssassin is one of the more popular spam filtering programs.
a9cc50d9
BW
110Get it from your local distribution or from the SpamAssassin web
111site at URL `http://spamassassin.org/'.
924df208 112
af435184
BW
113To use SpamAssassin, add the following recipes to
114\".procmailrc\":
924df208 115
25173d93 116 PATH=$PATH:/usr/bin/mh
f0d73c14 117 MAILDIR=$HOME/`mhparam Path`
924df208 118
f0d73c14
BW
119 # Fight spam with SpamAssassin.
120 :0fw
121 | spamc
924df208 122
f0d73c14
BW
123 # Anything with a spam level of 10 or more is junked immediately.
124 :0:
125 * ^X-Spam-Level: ..........
126 /dev/null
924df208 127
f0d73c14
BW
128 :0:
129 * ^X-Spam-Status: Yes
130 spam/.
924df208 131
5a4aad03 132If you don't use \"spamc\", use \"spamassassin -P -a\".
924df208 133
af435184
BW
134Note that one of the recipes above throws away messages with a
135score greater than or equal to 10. Here's how you can determine a
136value that works best for you.
924df208 137
af435184
BW
138First, run \"spamassassin -t\" on every mail message in your
139archive and use Gnumeric to verify that the average plus the
140standard deviation of good mail is under 5, the SpamAssassin
141default for \"spam\".
924df208 142
af435184
BW
143Using Gnumeric, sort the messages by score and view the messages
144with the highest score. Determine the score which encompasses all
145of your interesting messages and add a couple of points to be
146conservative. Add that many dots to the \"X-Spam-Level:\" header
147field above to send messages with that score down the drain.
924df208 148
af435184
BW
149In the example above, messages with a score of 5-9 are set aside
150in the \"+spam\" folder for later review. The major weakness of
151rules-based filters is a plethora of false positives so it is
152worthwhile to check.
924df208 153
af435184
BW
154If SpamAssassin classifies a message incorrectly, or is unsure,
155you can use the MH-E commands \\[mh-junk-blacklist] and
2dcf34f9 156\\[mh-junk-whitelist].
924df208 157
af435184
BW
158The command \\[mh-junk-blacklist] adds a \"blacklist_from\" entry
159to \"~/spamassassin/user_prefs\", deletes the message, and sends
160the message to the Razor, so that others might not see this spam.
161If the \"sa-learn\" command is available, the message is also
162recategorized as spam.
924df208 163
af435184
BW
164The command \\[mh-junk-whitelist] adds a \"whitelist_from\" rule
165to the \"~/.spamassassin/user_prefs\" file. If the \"sa-learn\"
166command is available, the message is also recategorized as ham.
2dcf34f9
BW
167
168Over time, you'll observe that the same host or domain occurs
af435184
BW
169repeatedly in the \"blacklist_from\" entries, so you might think
170that you could avoid future spam by blacklisting all mail from a
171particular domain. The utility function
172`mh-spamassassin-identify-spammers' helps you do precisely that.
173This function displays a frequency count of the hosts and domains
174in the \"blacklist_from\" entries from the last blank line in
175\"~/.spamassassin/user_prefs\" to the end of the file. This
2dcf34f9 176information can be used so that you can replace multiple
5a4aad03 177\"blacklist_from\" entries with a single wildcard entry such as:
924df208 178
f0d73c14 179 blacklist_from *@*amazingoffersdirect2u.com
924df208 180
2dcf34f9 181In versions of SpamAssassin (2.50 and on) that support a Bayesian
af435184
BW
182classifier, \\[mh-junk-blacklist] uses the program \"sa-learn\"
183to recategorize the message as spam. Neither MH-E, nor
184SpamAssassin, rebuilds the database after adding words, so you
185will need to run \"sa-learn --rebuild\" periodically. This can be
186done by adding the following to your crontab:
924df208 187
dda00b2c 188 0 * * * * sa-learn --rebuild > /dev/null 2>&1"
924df208 189 (unless mh-spamassassin-executable
f0d73c14 190 (error "Unable to find the spamassassin executable"))
924df208
BW
191 (let ((current-folder mh-current-folder)
192 (msg-file (mh-msg-filename msg mh-current-folder))
193 (sender))
a4de8c3d
SG
194 (message "Reporting message %d..." msg)
195 (mh-truncate-log-buffer)
196 ;; Put call-process output in log buffer if we are saving it
197 ;; (this happens if mh-junk-background is t).
198 (with-current-buffer mh-log-buffer
199 (call-process mh-spamassassin-executable msg-file mh-junk-background nil
f0d73c14
BW
200 ;;"--report" "--remove-from-whitelist"
201 "-r" "-R") ; spamassassin V2.20
a4de8c3d
SG
202 (when mh-sa-learn-executable
203 (message "Recategorizing message %d as spam..." msg)
204 (mh-truncate-log-buffer)
205 (call-process mh-sa-learn-executable msg-file mh-junk-background nil
206 "--single" "--spam" "--local" "--no-rebuild")))
207 (message "Blacklisting sender of message %d..." msg)
208 (with-current-buffer (get-buffer-create mh-temp-buffer)
924df208 209 (erase-buffer)
e495eaec 210 (call-process (expand-file-name mh-scan-prog mh-progs)
a4de8c3d
SG
211 nil t nil
212 (format "%d" msg) current-folder
924df208
BW
213 "-format" "%<(mymbox{from})%|%(addr{from})%>")
214 (goto-char (point-min))
215 (if (search-forward-regexp "^\\(.+\\)$" nil t)
216 (progn
217 (setq sender (match-string 0))
218 (mh-spamassassin-add-rule "blacklist_from" sender)
a4de8c3d
SG
219 (message "Blacklisting sender of message %d...done" msg))
220 (message "Blacklisting sender of message %d...not done (from my address)" msg)))))
924df208 221
dda00b2c 222;;;###mh-autoload
924df208 223(defun mh-spamassassin-whitelist (msg)
f0d73c14
BW
224 "Whitelist MSG with SpamAssassin.
225
5a4aad03
BW
226The \\[mh-junk-whitelist] command adds a \"whitelist_from\" rule to
227the \"~/.spamassassin/user_prefs\" file. If the \"sa-learn\" command
2dcf34f9 228is available, the message is also recategorized as ham.
f0d73c14
BW
229
230See `mh-spamassassin-blacklist' for more information."
924df208 231 (unless mh-spamassassin-executable
f0d73c14 232 (error "Unable to find the spamassassin executable"))
924df208
BW
233 (let ((msg-file (mh-msg-filename msg mh-current-folder))
234 (show-buffer (get-buffer mh-show-buffer))
235 from)
a4de8c3d 236 (with-current-buffer (get-buffer-create mh-temp-buffer)
924df208 237 (erase-buffer)
a4de8c3d
SG
238 (message "Removing spamassassin markup from message %d..." msg)
239 (call-process mh-spamassassin-executable msg-file t nil
f0d73c14
BW
240 ;; "--remove-markup"
241 "-d") ; spamassassin V2.20
924df208
BW
242 (if show-buffer
243 (kill-buffer show-buffer))
244 (write-file msg-file)
245 (when mh-sa-learn-executable
a4de8c3d
SG
246 (message "Recategorizing message %d as ham..." msg)
247 (mh-truncate-log-buffer)
248 ;; Put call-process output in log buffer if we are saving it
249 ;; (this happens if mh-junk-background is t).
250 (with-current-buffer mh-log-buffer
251 (call-process mh-sa-learn-executable msg-file mh-junk-background nil
252 "--single" "--ham" "--local" "--no-rebuild")))
253 (message "Whitelisting sender of message %d..." msg)
f0d73c14
BW
254 (setq from
255 (car (mh-funcall-if-exists
256 ietf-drums-parse-address (mh-get-header-field "From:"))))
924df208 257 (kill-buffer nil)
f0d73c14 258 (unless (or (null from) (equal from ""))
924df208 259 (mh-spamassassin-add-rule "whitelist_from" from))
a4de8c3d 260 (message "Whitelisting sender of message %d...done" msg))))
924df208
BW
261
262(defun mh-spamassassin-add-rule (rule body)
5a4aad03 263 "Add a new rule to \"~/.spamassassin/user_prefs\".
924df208
BW
264The name of the rule is RULE and its body is BODY."
265 (save-window-excursion
266 (let* ((line (format "%s\t%s\n" rule body))
267 (case-fold-search t)
268 (file (expand-file-name "~/.spamassassin/user_prefs"))
269 (buffer-exists (find-buffer-visiting file)))
270 (find-file file)
271 (if (not (search-forward (format "\n%s" line) nil t))
272 (progn
273 (goto-char (point-max))
274 (insert (if (bolp) "" "\n") line)
275 (save-buffer)))
276 (if (not buffer-exists)
277 (kill-buffer nil)))))
278
dda00b2c 279;;;###mh-autoload
924df208 280(defun mh-spamassassin-identify-spammers ()
f0d73c14 281 "Identify spammers who are repeat offenders.
924df208 282
2dcf34f9 283This function displays a frequency count of the hosts and domains
5a4aad03
BW
284in the \"blacklist_from\" entries from the last blank line in
285\"~/.spamassassin/user_prefs\" to the end of the file. This
2dcf34f9 286information can be used so that you can replace multiple
5a4aad03 287\"blacklist_from\" entries with a single wildcard entry such as:
924df208 288
f0d73c14 289 blacklist_from *@*amazingoffersdirect2u.com"
924df208
BW
290 (interactive)
291 (let* ((file (expand-file-name "~/.spamassassin/user_prefs"))
292 (domains (make-hash-table :test 'equal)))
293 (find-file file)
294 ;; Only consider entries between last blank line and end of file.
295 (goto-char (1- (point-max)))
296 (search-backward-regexp "^$")
297 ;; Perform frequency count.
298 (save-excursion
299 (while (search-forward-regexp "^blacklist_from\\s-*\\(.*\\)@\\(.*\\)$"
300 nil t)
301 (let ((host (match-string 2))
302 value)
303 ;; Remove top-level-domain from hostname.
304 (setq host (cdr (reverse (split-string host "\\."))))
305 ;; Add counts for each host and domain part.
306 (while host
307 (setq value (gethash (car host) domains))
f0d73c14 308 (setf (gethash (car host) domains) (1+ (if (not value) 0 value)))
924df208
BW
309 (setq host (cdr host))))))
310
311 ;; Output
312 (delete-other-windows)
313 (pop-to-buffer (get-buffer-create "*MH-E Spammer Frequencies*"))
314 (erase-buffer)
4f91a816
SM
315 (maphash (lambda (key value) ""
316 (if (> value 2)
317 (insert (format "%s %s\n" key value))))
924df208
BW
318 domains)
319 (sort-numeric-fields 2 (point-min) (point-max))
320 (reverse-region (point-min) (point-max))
321 (goto-char (point-min))))
322
f0d73c14
BW
323\f
324
325;; Bogofilter Interface
326
327(defvar mh-bogofilter-executable (executable-find "bogofilter"))
328
dda00b2c 329;;;###mh-autoload
f0d73c14 330(defun mh-bogofilter-blacklist (msg)
f09e2a44 331 "Blacklist MSG with bogofilter.
f0d73c14 332
2dcf34f9 333Bogofilter is a Bayesian spam filtering program. Get it from your
a9cc50d9
BW
334local distribution or from the bogofilter web site at URL
335`http://bogofilter.sourceforge.net/'.
f0d73c14
BW
336
337Bogofilter is taught by running:
338
339 bogofilter -n < good-message
340
341on every good message, and
342
343 bogofilter -s < spam-message
344
345on every spam message. This is called a full training; three other
2dcf34f9
BW
346training methods are described in the FAQ that is distributed with
347bogofilter. Note that most Bayesian filters need 1000 to 5000 of each
348type of message to start doing a good job.
f0d73c14 349
5a4aad03 350To use bogofilter, add the following recipes to \".procmailrc\":
f0d73c14 351
b4f8b162 352 PATH=$PATH:/usr/bin/mh
f0d73c14
BW
353 MAILDIR=$HOME/`mhparam Path`
354
f09e2a44 355 # Fight spam with bogofilter.
f0d73c14
BW
356 :0fw
357 | bogofilter -3 -e -p
358
359 :0:
360 * ^X-Bogosity: Yes, tests=bogofilter
361 spam/.
362
363 :0:
364 * ^X-Bogosity: Unsure, tests=bogofilter
365 spam/unsure/.
366
2dcf34f9
BW
367If bogofilter classifies a message incorrectly, or is unsure, you can
368use the MH-E commands \\[mh-junk-blacklist] and \\[mh-junk-whitelist]
369to update bogofilter's training.
f0d73c14
BW
370
371The \"Bogofilter FAQ\" suggests that you run the following
372occasionally to shrink the database:
373
374 bogoutil -d wordlist.db | bogoutil -l wordlist.db.new
375 mv wordlist.db wordlist.db.prv
376 mv wordlist.db.new wordlist.db
377
378The \"Bogofilter tuning HOWTO\" describes how you can fine-tune Bogofilter."
379 (unless mh-bogofilter-executable
380 (error "Unable to find the bogofilter executable"))
381 (let ((msg-file (mh-msg-filename msg mh-current-folder)))
a4de8c3d
SG
382 (mh-truncate-log-buffer)
383 ;; Put call-process output in log buffer if we are saving it
384 ;; (this happens if mh-junk-background is t).
385 (with-current-buffer mh-log-buffer
386 (call-process mh-bogofilter-executable msg-file mh-junk-background
387 nil "-s"))))
f0d73c14 388
dda00b2c 389;;;###mh-autoload
f0d73c14 390(defun mh-bogofilter-whitelist (msg)
f09e2a44 391 "Whitelist MSG with bogofilter.
f0d73c14
BW
392
393See `mh-bogofilter-blacklist' for more information."
394 (unless mh-bogofilter-executable
395 (error "Unable to find the bogofilter executable"))
396 (let ((msg-file (mh-msg-filename msg mh-current-folder)))
a4de8c3d
SG
397 (mh-truncate-log-buffer)
398 ;; Put call-process output in log buffer if we are saving it
399 ;; (this happens if mh-junk-background is t).
400 (with-current-buffer mh-log-buffer
401 (call-process mh-bogofilter-executable msg-file mh-junk-background
402 nil "-n"))))
f0d73c14
BW
403
404\f
405
406;; Spamprobe Interface
407
408(defvar mh-spamprobe-executable (executable-find "spamprobe"))
409
dda00b2c 410;;;###mh-autoload
f0d73c14
BW
411(defun mh-spamprobe-blacklist (msg)
412 "Blacklist MSG with SpamProbe.
413
a9cc50d9
BW
414SpamProbe is a Bayesian spam filtering program. Get it from your
415local distribution or from the SpamProbe web site at URL
416`http://spamprobe.sourceforge.net'.
f0d73c14 417
5a4aad03 418To use SpamProbe, add the following recipes to \".procmailrc\":
f0d73c14 419
b4f8b162 420 PATH=$PATH:/usr/bin/mh
f0d73c14
BW
421 MAILDIR=$HOME/`mhparam Path`
422
423 # Fight spam with SpamProbe.
424 :0
425 SCORE=| spamprobe receive
426
427 :0 wf
428 | formail -I \"X-SpamProbe: $SCORE\"
429
430 :0:
431 *^X-SpamProbe: SPAM
432 spam/.
433
2dcf34f9
BW
434If SpamProbe classifies a message incorrectly, you can use the
435MH-E commands \\[mh-junk-blacklist] and \\[mh-junk-whitelist] to
436update SpamProbe's training."
f0d73c14
BW
437 (unless mh-spamprobe-executable
438 (error "Unable to find the spamprobe executable"))
439 (let ((msg-file (mh-msg-filename msg mh-current-folder)))
a4de8c3d
SG
440 (mh-truncate-log-buffer)
441 ;; Put call-process output in log buffer if we are saving it
442 ;; (this happens if mh-junk-background is t).
443 (with-current-buffer mh-log-buffer
444 (call-process mh-spamprobe-executable msg-file mh-junk-background
445 nil "spam"))))
f0d73c14 446
dda00b2c 447;;;###mh-autoload
f0d73c14
BW
448(defun mh-spamprobe-whitelist (msg)
449 "Whitelist MSG with SpamProbe.
450
451See `mh-spamprobe-blacklist' for more information."
452 (unless mh-spamprobe-executable
453 (error "Unable to find the spamprobe executable"))
454 (let ((msg-file (mh-msg-filename msg mh-current-folder)))
a4de8c3d
SG
455 (mh-truncate-log-buffer)
456 ;; Put call-process output in log buffer if we are saving it
457 ;; (this happens if mh-junk-background is t).
458 (with-current-buffer mh-log-buffer
459 (call-process mh-spamprobe-executable msg-file mh-junk-background
460 nil "good"))))
f0d73c14 461
924df208
BW
462(provide 'mh-junk)
463
cee9f5c6
BW
464;; Local Variables:
465;; indent-tabs-mode: nil
466;; sentence-end-double-space: nil
467;; End:
924df208
BW
468
469;;; mh-junk.el ends here