;;; spam.el --- Identifying spam
-;; Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc.
+
+;; Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
-;; Keywords: network
+;; Maintainer: Ted Zlatanov <tzz@lifelogs.com>
+;; Keywords: network, spam, mail, bogofilter, BBDB, dspam, dig, whitelist, blacklist, gmane, hashcash, spamassassin, bsfilter, ifile, stat, crm114, spamoracle
;; This file is part of GNU Emacs.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
;;; Commentary:
(eval-when-compile (require 'cl))
(eval-when-compile (require 'spam-report))
+(eval-when-compile (require 'hashcash))
(require 'gnus-sum)
;; autoload spam-report
(eval-and-compile
(autoload 'spam-report-gmane "spam-report")
+ (autoload 'spam-report-gmane-spam "spam-report")
+ (autoload 'spam-report-gmane-ham "spam-report")
(autoload 'spam-report-resend "spam-report"))
;; autoload gnus-registry
Populated by spam-install-backend-super.")
(defgroup spam nil
- "Spam configuration.")
+ "Spam configuration."
+ :version "22.1"
+ :group 'mail
+ :group 'news)
(defcustom spam-summary-exit-behavior 'default
"Exit behavior at the time of summary exit.
(defcustom spam-mark-new-messages-in-spam-group-as-spam t
"Whether new messages in a spam group should get the spam-mark."
:type 'boolean
+ ;; :version "22.1" ;; Gnus 5.10.8 / No Gnus 0.3
:group 'spam)
(defcustom spam-log-to-registry nil
(defcustom spam-blackhole-good-server-regex nil
"String matching IP addresses that should not be checked in the blackholes.
Only meaningful if you enable `spam-use-blackholes'."
- :type '(radio (const nil)
- (regexp :format "%t: %v\n" :size 0))
+ :type '(radio (const nil) regexp)
+ :group 'spam)
+
+(defface spam
+ '((((class color) (type tty) (background dark))
+ (:foreground "gray80" :background "gray50"))
+ (((class color) (type tty) (background light))
+ (:foreground "gray50" :background "gray80"))
+ (((class color) (background dark))
+ (:foreground "ivory2"))
+ (((class color) (background light))
+ (:foreground "ivory4"))
+ (t :inverse-video t))
+ "Face for spam-marked articles."
:group 'spam)
+;; backward-compatibility alias
+(put 'spam-face 'face-alias 'spam)
-(defcustom spam-face 'gnus-splash-face
+(defcustom spam-face 'spam
"Face for spam-marked articles."
:type 'face
:group 'spam)
:type '(repeat (regexp :tag "Regular expression to match ham body"))
:group 'spam)
+(defcustom spam-summary-score-preferred-header nil
+ "Preferred header to use for spam-summary-score."
+ :type '(choice :tag "Header name"
+ (symbol :tag "SpamAssassin etc" X-Spam-Status)
+ (symbol :tag "Bogofilter" X-Bogosity)
+ (const :tag "No preference, take best guess." nil))
+ :group 'spam)
+
(defgroup spam-ifile nil
"Spam ifile configuration."
:group 'spam)
(const :tag "Bogofilter is not installed"))
:group 'spam-bogofilter)
+(defvar spam-bogofilter-valid 'unknown "Is the bogofilter version valid?")
+
(defcustom spam-bogofilter-header "X-Bogosity"
"The header that Bogofilter inserts in messages."
:type 'string
classification
type)))
+(defun spam-backend-article-list-property (classification
+ &optional unregister)
+ "Property name of article list with CLASSIFICATION and UNREGISTER."
+ (let* ((r (if unregister "unregister" "register"))
+ (prop (format "%s-%s" classification r)))
+ prop))
+
+(defun spam-backend-get-article-todo-list (backend
+ classification
+ &optional unregister)
+ "Get the articles to be processed for BACKEND and CLASSIFICATION.
+With UNREGISTER, get articles to be unregistered.
+This is a temporary storage function - nothing here persists."
+ (get
+ backend
+ (intern (spam-backend-article-list-property classification unregister))))
+
+(defun spam-backend-put-article-todo-list (backend classification list &optional unregister)
+ "Set the LIST of articles to be processed for BACKEND and CLASSIFICATION.
+With UNREGISTER, set articles to be unregistered.
+This is a temporary storage function - nothing here persists."
+ (put
+ backend
+ (intern (spam-backend-article-list-property classification unregister))
+ list))
+
(defun spam-backend-ham-registration-function (backend)
"Get the ham registration function for BACKEND."
(get backend 'hrf))
(spam-install-checkonly-backend 'spam-use-blackholes
'spam-check-blackholes)
-;; TODO: does anyone use hashcash? We should remove it if not.
(spam-install-checkonly-backend 'spam-use-hashcash
'spam-check-hashcash)
nil)
(spam-install-nocheck-backend 'spam-use-gmane
- nil
+ 'spam-report-gmane-unregister-routine
'spam-report-gmane-register-routine
- ;; does Gmane support unregistration?
- nil
- nil)
+ 'spam-report-gmane-register-routine
+ 'spam-report-gmane-unregister-routine)
(spam-install-nocheck-backend 'spam-use-resend
'spam-report-resend-register-ham-routine
spam-use-spamassassin-headers
spam-use-regex-headers)
(push 'X-Spam-Status list))
+ (when spam-use-bogofilter
+ (push 'X-Bogosity list))
+ (when spam-use-crm114
+ (push 'X-CRM114-Status list))
list))
(defun spam-user-format-function-S (headers)
(when headers
- (spam-summary-score headers)))
+ (format "%3.2f"
+ (spam-summary-score headers spam-summary-score-preferred-header))))
(defun spam-article-sort-by-spam-status (h1 h2)
"Sort articles by score."
(return))))
result))
+(defvar spam-spamassassin-score-regexp
+ ".*\\b\\(?:score\\|hits\\)=\\(-?[0-9.]+\\)"
+ "Regexp matching SpamAssassin score header.
+The first group must match the number.")
+;; "score" for Spamassassin 3.0 or later:
+;; X-Spam-Status: Yes, score=13.1 required=5.0 tests=DNS_FROM_RFC_ABUSE,
+;; [...],UNDISC_RECIPS autolearn=disabled version=3.0.3
+
+
(defun spam-extra-header-to-number (header headers)
- "Transform an extra header to a number."
- (if (gnus-extra-header header headers)
- (cond
- ((eq header 'X-Spam-Status)
- (string-to-number (gnus-replace-in-string
- (gnus-extra-header header headers)
- ".*hits=" "")))
- ;; for CRM checking, it's probably faster to just do the string match
- ((and spam-use-crm114 (string-match "( pR: \\([0-9.-]+\\)" header))
- (match-string 1 header))
- (t nil))
- nil))
+ "Transform an extra HEADER to a number, using list of HEADERS.
+Note this has to be fast."
+ (let ((header-content (gnus-extra-header header headers)))
+ (if header-content
+ (cond
+ ((eq header 'X-Spam-Status)
+ (string-to-number (gnus-replace-in-string
+ header-content
+ spam-spamassassin-score-regexp
+ "\\1")))
+ ;; for CRM checking, it's probably faster to just do the string match
+ ((and spam-use-crm114 (string-match "( pR: \\([0-9.-]+\\)" header-content))
+ (string-to-number (match-string 1 header-content)))
+ ((eq header 'X-Bogosity)
+ (string-to-number (gnus-replace-in-string
+ (gnus-replace-in-string
+ header-content
+ ".*spamicity=" "")
+ ",.*" "")))
+ (t nil))
+ nil)))
(defun spam-summary-score (headers &optional specific-header)
"Score an article for the summary buffer, as fast as possible.
(gnus-group-spam-exit-processor-stat spam spam-use-stat)
(gnus-group-spam-exit-processor-spamoracle spam spam-use-spamoracle)
(gnus-group-spam-exit-processor-spamassassin spam spam-use-spamassassin)
+ (gnus-group-spam-exit-processor-report-gmane spam spam-use-gmane) ;; Buggy?
(gnus-group-ham-exit-processor-ifile ham spam-use-ifile)
(gnus-group-ham-exit-processor-bogofilter ham spam-use-bogofilter)
(gnus-group-ham-exit-processor-bsfilter ham spam-use-bsfilter)
(unless gnus-group-is-exiting-without-update-p
(gnus-message 6 "Exiting summary buffer and applying spam rules")
+ ;; before we begin, remove any article limits
+; (ignore-errors
+; (gnus-summary-pop-limit t))
+
;; first of all, unregister any articles that are no longer ham or spam
;; we have to iterate over the processors, or else we'll be too slow
(dolist (classification (spam-classifications))
;; call spam-register-routine with specific articles to unregister,
;; when there are articles to unregister and the check is enabled
(when (and unregister-list (symbol-value backend))
- (spam-unregister-routine
- classification
- backend
- unregister-list))))))
+ (spam-backend-put-article-todo-list backend
+ classification
+ unregister-list
+ t))))))
;; do the non-moving backends first, then the moving ones
(dolist (backend-type '(non-mover mover))
- (dolist (classification '(spam ham))
+ (dolist (classification (spam-classifications))
(dolist (backend (spam-backend-list backend-type))
(when (spam-group-processor-p
gnus-newsgroup-name
backend
classification)
- (let ((num (spam-register-routine classification backend)))
- (when (> num 0)
- (gnus-message
- 6
- "%d %s messages were processed by backend %s."
- num
- classification
- backend)))))))
+ (spam-backend-put-article-todo-list backend
+ classification
+ (spam-list-articles
+ gnus-newsgroup-articles
+ classification))))))
+
+ (spam-resolve-registrations-routine) ; do the registrations now
;; we mark all the leftover spam articles as expired at the end
(dolist (article (spam-list-articles
;;{{{ registration/unregistration functions
+(defun spam-resolve-registrations-routine ()
+ "Go through the backends and register or unregister articles as needed."
+ (dolist (backend-type '(non-mover mover))
+ (dolist (classification (spam-classifications))
+ (dolist (backend (spam-backend-list backend-type))
+ (let ((rlist (spam-backend-get-article-todo-list
+ backend classification))
+ (ulist (spam-backend-get-article-todo-list
+ backend classification t))
+ (delcount 0))
+
+ ;; clear the old lists right away
+ (spam-backend-put-article-todo-list backend
+ classification
+ nil
+ nil)
+ (spam-backend-put-article-todo-list backend
+ classification
+ nil
+ t)
+
+ ;; eliminate duplicates
+ (dolist (article (copy-sequence ulist))
+ (when (memq article rlist)
+ (incf delcount)
+ (setq rlist (delq article rlist))
+ (setq ulist (delq article ulist))))
+
+ (unless (zerop delcount)
+ (gnus-message
+ 9
+ "%d messages were saved the trouble of unregistering and then registering"
+ delcount))
+
+ ;; unregister articles
+ (unless (zerop (length ulist))
+ (let ((num (spam-unregister-routine classification backend ulist)))
+ (when (> num 0)
+ (gnus-message
+ 6
+ "%d %s messages were unregistered by backend %s."
+ num
+ classification
+ backend))))
+
+ ;; register articles
+ (unless (zerop (length rlist))
+ (let ((num (spam-register-routine classification backend rlist)))
+ (when (> num 0)
+ (gnus-message
+ 6
+ "%d %s messages were registered by backend %s."
+ num
+ classification
+ backend)))))))))
+
(defun spam-unregister-routine (classification
- backend
- &optional specific-articles)
- (spam-register-routine classification backend t specific-articles))
+ backend
+ specific-articles)
+ (spam-register-routine classification backend specific-articles t))
(defun spam-register-routine (classification
- backend
- &optional unregister
- specific-articles)
+ backend
+ specific-articles
+ &optional unregister)
(when (and (spam-classification-valid-p classification)
(spam-backend-valid-p backend))
(let* ((register-function
classification
backend)
(funcall run-function articles)
- ;; now log all the registrations (or undo them, depending on unregister)
+ ;; now log all the registrations (or undo them, depending on
+ ;; unregister)
(dolist (article articles)
(funcall log-function
(spam-fetch-field-message-id-fast article)
classification
backend
gnus-newsgroup-name))))
- (length articles)))) ;return the number of articles processed
+ ;; return the number of articles processed
+ (length articles))))
;;; log a ham- or spam-processor invocation to the registry
(defun spam-log-processing-to-registry (id type classification backend group)
;;{{{ Hashcash.
-(condition-case nil
- (progn
- (require 'hashcash)
-
- (defun spam-check-hashcash ()
- "Check the headers for hashcash payments."
- (mail-check-payment))) ;mail-check-payment returns a boolean
+(defun spam-check-hashcash ()
+ "Check the headers for hashcash payments."
+ (ignore-errors (mail-check-payment))) ;mail-check-payment returns a boolean
- (file-error (progn
- (defalias 'mail-check-payment 'ignore)
- (defalias 'spam-check-hashcash 'ignore))))
;;}}}
;;{{{ BBDB
;; all this is done inside a condition-case to trap errors
-(condition-case nil
- (progn
- (require 'bbdb)
- (require 'bbdb-com)
-
- ;; when the BBDB changes, we want to clear out our cache
- (defun spam-clear-cache-BBDB (&rest immaterial)
- (spam-clear-cache 'spam-use-BBDB))
-
- (add-hook 'bbdb-change-hook 'spam-clear-cache-BBDB)
-
- (defun spam-enter-ham-BBDB (addresses &optional remove)
- "Enter an address into the BBDB; implies ham (non-spam) sender"
- (dolist (from addresses)
- (when (stringp from)
- (let* ((parsed-address (gnus-extract-address-components from))
- (name (or (nth 0 parsed-address) "Ham Sender"))
- (remove-function (if remove
- 'bbdb-delete-record-internal
- 'ignore))
- (net-address (nth 1 parsed-address))
- (record (and net-address
- (bbdb-search-simple nil net-address))))
- (when net-address
- (gnus-message 6 "%s address %s %s BBDB"
- (if remove "Deleting" "Adding")
- from
- (if remove "from" "to"))
- (if record
- (funcall remove-function record)
- (bbdb-create-internal name nil net-address nil nil
- "ham sender added by spam.el")))))))
-
- (defun spam-BBDB-register-routine (articles &optional unregister)
- (let (addresses)
- (dolist (article articles)
- (when (stringp (spam-fetch-field-from-fast article))
- (push (spam-fetch-field-from-fast article) addresses)))
- ;; now do the register/unregister action
- (spam-enter-ham-BBDB addresses unregister)))
-
- (defun spam-BBDB-unregister-routine (articles)
- (spam-BBDB-register-routine articles t))
-
- (defun spam-check-BBDB ()
- "Mail from people in the BBDB is classified as ham or non-spam"
- (let ((who (message-fetch-field "from"))
- bbdb-cache bbdb-hashtable)
- (when spam-cache-lookups
- (setq bbdb-cache (gethash 'spam-use-BBDB spam-caches))
- (unless bbdb-cache
- (setq bbdb-cache
- ;; this is the expanded (bbdb-hashtable) macro
- ;; without the debugging support
- (with-current-buffer (bbdb-buffer)
- (save-excursion
- (save-window-excursion
- (bbdb-records nil t)
- bbdb-hashtable))))
- (puthash 'spam-use-BBDB bbdb-cache spam-caches)))
- (when who
- (setq who (nth 1 (gnus-extract-address-components who)))
- (if
- (if spam-cache-lookups
- (symbol-value
- (intern-soft who bbdb-cache))
- (bbdb-search-simple nil who))
- t
- (if spam-use-BBDB-exclusive
- spam-split-group
- nil))))))
-
- (file-error (progn
- (defalias 'bbdb-search-simple 'ignore)
- (defalias 'bbdb-records 'ignore)
- (defalias 'bbdb-buffer 'ignore)
- (defalias 'spam-check-BBDB 'ignore)
- (defalias 'spam-BBDB-register-routine 'ignore)
- (defalias 'spam-enter-ham-BBDB 'ignore)
- (defalias 'bbdb-create-internal 'ignore)
- (defalias 'bbdb-delete-record-internal 'ignore)
- (defalias 'bbdb-records 'ignore))))
+(eval-when-compile
+ (autoload 'bbdb-buffer "bbdb")
+ (autoload 'bbdb-create-internal "bbdb")
+ (autoload 'bbdb-search-simple "bbdb"))
+
+(eval-and-compile
+ (when (condition-case nil
+ (progn
+ (require 'bbdb)
+ (require 'bbdb-com))
+ (file-error
+ ;; `bbdb-records' should not be bound as an autoload function
+ ;; before loading bbdb because of `bbdb-hashtable-size'.
+ (defalias 'bbdb-records 'ignore)
+ (defalias 'spam-BBDB-register-routine 'ignore)
+ (defalias 'spam-enter-ham-BBDB 'ignore)
+ nil))
+
+ ;; when the BBDB changes, we want to clear out our cache
+ (defun spam-clear-cache-BBDB (&rest immaterial)
+ (spam-clear-cache 'spam-use-BBDB))
+
+ (add-hook 'bbdb-change-hook 'spam-clear-cache-BBDB)
+
+ (defun spam-enter-ham-BBDB (addresses &optional remove)
+ "Enter an address into the BBDB; implies ham (non-spam) sender"
+ (dolist (from addresses)
+ (when (stringp from)
+ (let* ((parsed-address (gnus-extract-address-components from))
+ (name (or (nth 0 parsed-address) "Ham Sender"))
+ (remove-function (if remove
+ 'bbdb-delete-record-internal
+ 'ignore))
+ (net-address (nth 1 parsed-address))
+ (record (and net-address
+ (bbdb-search-simple nil net-address))))
+ (when net-address
+ (gnus-message 6 "%s address %s %s BBDB"
+ (if remove "Deleting" "Adding")
+ from
+ (if remove "from" "to"))
+ (if record
+ (funcall remove-function record)
+ (bbdb-create-internal name nil net-address nil nil
+ "ham sender added by spam.el")))))))
+
+ (defun spam-BBDB-register-routine (articles &optional unregister)
+ (let (addresses)
+ (dolist (article articles)
+ (when (stringp (spam-fetch-field-from-fast article))
+ (push (spam-fetch-field-from-fast article) addresses)))
+ ;; now do the register/unregister action
+ (spam-enter-ham-BBDB addresses unregister)))
+
+ (defun spam-BBDB-unregister-routine (articles)
+ (spam-BBDB-register-routine articles t))
+
+ (defun spam-check-BBDB ()
+ "Mail from people in the BBDB is classified as ham or non-spam"
+ (let ((who (message-fetch-field "from"))
+ bbdb-cache bbdb-hashtable)
+ (when spam-cache-lookups
+ (setq bbdb-cache (gethash 'spam-use-BBDB spam-caches))
+ (unless bbdb-cache
+ (setq bbdb-cache (make-vector 17 0)) ; a good starting hash value
+ ;; this is based on the expanded (bbdb-hashtable) macro
+ ;; without the debugging support
+ (with-current-buffer (bbdb-buffer)
+ (save-excursion
+ (save-window-excursion
+ (bbdb-records nil t)
+ (mapatoms
+ (lambda (symbol)
+ (intern (downcase (symbol-name symbol)) bbdb-cache))
+ bbdb-hashtable))))
+ (puthash 'spam-use-BBDB bbdb-cache spam-caches)))
+ (when who
+ (setq who (nth 1 (gnus-extract-address-components who)))
+ (if
+ (if spam-cache-lookups
+ (intern-soft (downcase who) bbdb-cache)
+ (bbdb-search-simple nil who))
+ t
+ (if spam-use-BBDB-exclusive
+ spam-split-group
+ nil)))))))
;;}}}
;;{{{ spam-stat
-(condition-case nil
- (progn
- (let ((spam-stat-install-hooks nil))
- (require 'spam-stat))
+(eval-when-compile
+ (autoload 'spam-stat-buffer-change-to-non-spam "spam-stat")
+ (autoload 'spam-stat-buffer-change-to-spam "spam-stat")
+ (autoload 'spam-stat-buffer-is-non-spam "spam-stat")
+ (autoload 'spam-stat-buffer-is-spam "spam-stat")
+ (autoload 'spam-stat-load "spam-stat")
+ (autoload 'spam-stat-save "spam-stat")
+ (autoload 'spam-stat-split-fancy "spam-stat"))
- (defun spam-check-stat ()
- "Check the spam-stat backend for the classification of this message"
- (let ((spam-stat-split-fancy-spam-group spam-split-group) ; override
- (spam-stat-buffer (buffer-name)) ; stat the current buffer
- category return)
- (spam-stat-split-fancy)))
-
- (defun spam-stat-register-spam-routine (articles &optional unregister)
- (dolist (article articles)
- (let ((article-string (spam-get-article-as-string article)))
- (with-temp-buffer
- (insert article-string)
- (if unregister
- (spam-stat-buffer-change-to-non-spam)
+(eval-and-compile
+ (when (condition-case nil
+ (let ((spam-stat-install-hooks nil))
+ (require 'spam-stat))
+ (file-error
+ (defalias 'spam-stat-register-ham-routine 'ignore)
+ (defalias 'spam-stat-register-spam-routine 'ignore)
+ nil))
+
+ (defun spam-check-stat ()
+ "Check the spam-stat backend for the classification of this message"
+ (let ((spam-stat-split-fancy-spam-group spam-split-group) ; override
+ (spam-stat-buffer (buffer-name)) ; stat the current buffer
+ category return)
+ (spam-stat-split-fancy)))
+
+ (defun spam-stat-register-spam-routine (articles &optional unregister)
+ (dolist (article articles)
+ (let ((article-string (spam-get-article-as-string article)))
+ (with-temp-buffer
+ (insert article-string)
+ (if unregister
+ (spam-stat-buffer-change-to-non-spam)
(spam-stat-buffer-is-spam))))))
- (defun spam-stat-unregister-spam-routine (articles)
- (spam-stat-register-spam-routine articles t))
+ (defun spam-stat-unregister-spam-routine (articles)
+ (spam-stat-register-spam-routine articles t))
- (defun spam-stat-register-ham-routine (articles &optional unregister)
- (dolist (article articles)
- (let ((article-string (spam-get-article-as-string article)))
- (with-temp-buffer
- (insert article-string)
- (if unregister
- (spam-stat-buffer-change-to-spam)
+ (defun spam-stat-register-ham-routine (articles &optional unregister)
+ (dolist (article articles)
+ (let ((article-string (spam-get-article-as-string article)))
+ (with-temp-buffer
+ (insert article-string)
+ (if unregister
+ (spam-stat-buffer-change-to-spam)
(spam-stat-buffer-is-non-spam))))))
- (defun spam-stat-unregister-ham-routine (articles)
- (spam-stat-register-ham-routine articles t))
-
- (defun spam-maybe-spam-stat-load ()
- (when spam-use-stat (spam-stat-load)))
-
- (defun spam-maybe-spam-stat-save ()
- (when spam-use-stat (spam-stat-save))))
-
- (file-error (progn
- (defalias 'spam-stat-load 'ignore)
- (defalias 'spam-stat-save 'ignore)
- (defalias 'spam-maybe-spam-stat-load 'ignore)
- (defalias 'spam-maybe-spam-stat-save 'ignore)
- (defalias 'spam-stat-register-ham-routine 'ignore)
- (defalias 'spam-stat-unregister-ham-routine 'ignore)
- (defalias 'spam-stat-register-spam-routine 'ignore)
- (defalias 'spam-stat-unregister-spam-routine 'ignore)
- (defalias 'spam-stat-buffer-is-spam 'ignore)
- (defalias 'spam-stat-buffer-change-to-spam 'ignore)
- (defalias 'spam-stat-buffer-is-non-spam 'ignore)
- (defalias 'spam-stat-buffer-change-to-non-spam 'ignore)
- (defalias 'spam-stat-split-fancy 'ignore)
- (defalias 'spam-check-stat 'ignore))))
+ (defun spam-stat-unregister-ham-routine (articles)
+ (spam-stat-register-ham-routine articles t))
+ (defun spam-maybe-spam-stat-load ()
+ (when spam-use-stat (spam-stat-load)))
+ (defun spam-maybe-spam-stat-save ()
+ (when spam-use-stat (spam-stat-save)))))
;;}}}
;;{{{ Spam-report glue (gmane and resend reporting)
(defun spam-report-gmane-register-routine (articles)
(when articles
- (apply 'spam-report-gmane articles)))
+ (apply 'spam-report-gmane-spam articles)))
+
+(defun spam-report-gmane-unregister-routine (articles)
+ (when articles
+ (apply 'spam-report-gmane-ham articles)))
(defun spam-report-resend-register-ham-routine (articles)
(spam-report-resend-register-routine articles t))
(message "Spamicity score %s" score)
(or score "0"))))
+(defun spam-verify-bogofilter ()
+ "Verify the Bogofilter version is sufficient."
+ (when (eq spam-bogofilter-valid 'unknown)
+ (setq spam-bogofilter-valid
+ (not (string-match "^bogofilter version 0\\.\\([0-9]\\|1[01]\\)\\."
+ (shell-command-to-string
+ (format "%s -V" spam-bogofilter-path))))))
+ spam-bogofilter-valid)
+
(defun spam-check-bogofilter (&optional score)
- "Check the Bogofilter backend for the classification of this message"
- (let ((article-buffer-name (buffer-name))
- (db spam-bogofilter-database-directory)
+ "Check the Bogofilter backend for the classification of this message."
+ (if (spam-verify-bogofilter)
+ (let ((article-buffer-name (buffer-name))
+ (db spam-bogofilter-database-directory)
+ return)
+ (with-temp-buffer
+ (let ((temp-buffer-name (buffer-name)))
+ (save-excursion
+ (set-buffer article-buffer-name)
+ (apply 'call-process-region
+ (point-min) (point-max)
+ spam-bogofilter-path
+ nil temp-buffer-name nil
+ (if db `("-d" ,db "-v") `("-v"))))
+ (setq return (spam-check-bogofilter-headers score))))
return)
- (with-temp-buffer
- (let ((temp-buffer-name (buffer-name)))
- (save-excursion
- (set-buffer article-buffer-name)
- (apply 'call-process-region
- (point-min) (point-max)
- spam-bogofilter-path
- nil temp-buffer-name nil
- (if db `("-d" ,db "-v") `("-v"))))
- (setq return (spam-check-bogofilter-headers score))))
- return))
+ (gnus-error 5 "`spam.el' doesn't support obsolete bogofilter versions")))
(defun spam-bogofilter-register-with-bogofilter (articles
spam
&optional unregister)
"Register an article, given as a string, as spam or non-spam."
- (dolist (article articles)
- (let ((article-string (spam-get-article-as-string article))
- (db spam-bogofilter-database-directory)
- (switch (if unregister
- (if spam
- spam-bogofilter-spam-strong-switch
- spam-bogofilter-ham-strong-switch)
- (if spam
- spam-bogofilter-spam-switch
- spam-bogofilter-ham-switch))))
- (when (stringp article-string)
- (with-temp-buffer
- (insert article-string)
-
- (apply 'call-process-region
- (point-min) (point-max)
- spam-bogofilter-path
- nil nil nil switch
- (if db `("-d" ,db "-v") `("-v"))))))))
+ (if (spam-verify-bogofilter)
+ (dolist (article articles)
+ (let ((article-string (spam-get-article-as-string article))
+ (db spam-bogofilter-database-directory)
+ (switch (if unregister
+ (if spam
+ spam-bogofilter-spam-strong-switch
+ spam-bogofilter-ham-strong-switch)
+ (if spam
+ spam-bogofilter-spam-switch
+ spam-bogofilter-ham-switch))))
+ (when (stringp article-string)
+ (with-temp-buffer
+ (insert article-string)
+
+ (apply 'call-process-region
+ (point-min) (point-max)
+ spam-bogofilter-path
+ nil nil nil switch
+ (if db `("-d" ,db "-v") `("-v")))))))
+ (gnus-error 5 "`spam.el' doesn't support obsolete bogofilter versions")))
(defun spam-bogofilter-register-spam-routine (articles &optional unregister)
(spam-bogofilter-register-with-bogofilter articles t unregister))
(if score ; scoring mode
(let ((header (message-fetch-field spam-spamassassin-spam-status-header)))
(when header
- (if (string-match "hits=\\(-?[0-9.]+\\)" header)
+ (if (string-match spam-spamassassin-score-regexp header)
(match-string 1 header)
"0")))
;; spam detection mode
(add-to-list 'gnus-extra-headers header))
(setq spam-install-hooks t)
- ;; TODO: How do we redo this every time spam-face is customized?
- (push '((eq mark gnus-spam-mark) . spam-face)
+ ;; TODO: How do we redo this every time the `spam' face is customized?
+ (push '((eq mark gnus-spam-mark) . spam)
gnus-summary-highlight)
;; Add hooks for loading and saving the spam stats
(add-hook 'gnus-save-newsrc-hook 'spam-maybe-spam-stat-save)
(remove-hook 'gnus-get-new-news-hook 'spam-setup-widening)
(remove-hook 'gnus-summary-prepare-hook 'spam-find-spam))
+(add-hook 'spam-unload-hook 'spam-unload-hook)
+
(when spam-install-hooks
(spam-initialize))
;;}}}