mh-junk.el 17 KB

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