;;; Several TODO items are marked as such
-;; TODO: spam scores, detection of spam in newsgroups, cross-server splitting,
-;; remote processing, training through files
+;; TODO: cross-server splitting, remote processing, training through files
;;; Code:
+;;{{{ compilation directives and autoloads/requires
+
(eval-when-compile (require 'cl))
+(eval-when-compile (require 'spam-report))
(require 'gnus-sum)
;; autoload spam-report
(eval-and-compile
- (autoload 'spam-report-gmane "spam-report"))
+ (autoload 'spam-report-gmane "spam-report")
+ (autoload 'spam-report-resend "spam-report"))
;; autoload gnus-registry
(eval-and-compile
(eval-and-compile
(autoload 'query-dns "dns"))
-;;; Main parameters.
+;;}}}
+
+;;{{{ Main parameters.
+(defvar spam-backends nil
+ "List of spam.el backends with all the pertinent data.
+Populated by spam-install-backend-super.")
(defgroup spam nil
- "Spam configuration.")
+ "Spam configuration."
+ :version "22.1")
+
+(defcustom spam-summary-exit-behavior 'default
+ "Exit behavior at the time of summary exit.
+Note that setting the spam-use-move or spam-use-copy backends on
+a group through group/topic parameters overrides this mechanism."
+ :type '(choice (const 'default :tag
+ "Move spam out of all groups. Move ham out of spam groups.")
+ (const 'move-all :tag
+ "Move spam out of all groups. Move ham out of all groups.")
+ (const 'move-none :tag
+ "Never move spam or ham out of any groups."))
+ :group 'spam)
-(defcustom spam-directory "~/News/spam/"
+(defcustom spam-directory (nnheader-concat gnus-directory "spam/")
"Directory for spam whitelists and blacklists."
:type 'directory
:group 'spam)
-(defcustom spam-move-spam-nonspam-groups-only t
- "Whether spam should be moved in non-spam groups only.
-When t, only ham and unclassified groups will have their spam moved
-to the spam-process-destination. When nil, spam will also be moved from
-spam groups."
- :type 'boolean
- :group 'spam)
-
-(defcustom spam-process-ham-in-nonham-groups nil
- "Whether ham should be processed in non-ham groups."
+(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
:group 'spam)
:type 'boolean
:group 'spam)
-(defcustom spam-process-ham-in-spam-groups nil
- "Whether ham should be processed in spam groups."
- :type 'boolean
- :group 'spam)
-
(defcustom spam-mark-only-unseen-as-spam t
"Whether only unseen articles should be marked as spam in spam groups.
When nil, all unread articles in a spam group are marked as
:type 'boolean
:group 'spam)
+(defcustom spam-use-gmane-xref nil
+ "Whether the Gmane spam xref should be used by `spam-split'."
+ :type 'boolean
+ :group 'spam)
+
(defcustom spam-use-blacklist nil
"Whether the blacklist should be used by `spam-split'."
:type 'boolean
:type 'boolean
:group 'spam)
+(defcustom spam-use-bsfilter-headers nil
+ "Whether bsfilter headers should be used by `spam-split'.
+Enable this if you pre-process messages with Bsfilter BEFORE Gnus sees them."
+ :type 'boolean
+ :group 'spam)
+
+(defcustom spam-use-bsfilter nil
+ "Whether bsfilter should be invoked by `spam-split'.
+Enable this if you want Gnus to invoke Bsfilter on new messages."
+ :type 'boolean
+ :group 'spam)
+
(defcustom spam-use-BBDB nil
"Whether BBDB should be used by `spam-split'."
:type 'boolean
:type 'boolean
:group 'spam)
+(defcustom spam-use-crm114 nil
+ "Whether the CRM114 Mailfilter should be used by `spam-split'."
+ :type 'boolean
+ :group 'spam)
+
(defcustom spam-install-hooks (or
spam-use-dig
+ spam-use-gmane-xref
spam-use-blacklist
spam-use-whitelist
spam-use-whitelist-exclusive
spam-use-bogofilter-headers
spam-use-spamassassin
spam-use-spamassassin-headers
+ spam-use-bsfilter
+ spam-use-bsfilter-headers
spam-use-BBDB
spam-use-BBDB-exclusive
spam-use-ifile
spam-use-stat
- spam-use-spamoracle)
+ spam-use-spamoracle
+ spam-use-crm114)
"Whether the spam hooks should be installed.
Default to t if one of the spam-use-* variables is set."
:group 'spam
:type '(repeat (string :tag "Group"))
:group 'spam)
+
+(defcustom spam-gmane-xref-spam-group "gmane.spam.detected"
+ "The group where spam xrefs can be found on Gmane.
+Only meaningful if you enable `spam-use-gmane-xref'."
+ :type 'string
+ :group 'spam)
+
(defcustom spam-blackhole-servers '("bl.spamcop.net" "relays.ordb.org"
"dev.null.dk" "relays.visi.com")
- "List of blackhole servers."
+ "List of blackhole servers.
+Only meaningful if you enable `spam-use-blackholes'."
:type '(repeat (string :tag "Server"))
:group 'spam)
(defcustom spam-blackhole-good-server-regex nil
- "String matching IP addresses that should not be checked in the blackholes."
- :type '(radio (const nil)
- (regexp :format "%t: %v\n" :size 0))
+ "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)
:group 'spam)
-(defcustom spam-face 'gnus-splash-face
+(defface spam-face
+ '((((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.")
+
+(defcustom spam-face 'spam-face
"Face for spam-marked articles."
:type 'face
:group 'spam)
(defcustom spam-regex-headers-spam '("^X-Spam-Flag: YES")
- "Regular expression for positive header spam matches."
+ "Regular expression for positive header spam matches.
+Only meaningful if you enable `spam-use-regex-headers'."
:type '(repeat (regexp :tag "Regular expression to match spam header"))
:group 'spam)
(defcustom spam-regex-headers-ham '("^X-Spam-Flag: NO")
- "Regular expression for positive header ham matches."
+ "Regular expression for positive header ham matches.
+Only meaningful if you enable `spam-use-regex-headers'."
:type '(repeat (regexp :tag "Regular expression to match ham header"))
:group 'spam)
(defcustom spam-regex-body-spam '()
- "Regular expression for positive body spam matches."
+ "Regular expression for positive body spam matches.
+Only meaningful if you enable `spam-use-regex-body'."
:type '(repeat (regexp :tag "Regular expression to match spam body"))
:group 'spam)
(defcustom spam-regex-body-ham '()
- "Regular expression for positive body ham matches."
+ "Regular expression for positive body ham matches.
+Only meaningful if you enable `spam-use-regex-body'."
: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
(const :tag "Use the default"))
:group 'spam-bogofilter)
+(defgroup spam-bsfilter nil
+ "Spam bsfilter configuration."
+ :group 'spam)
+
+(defcustom spam-bsfilter-path (executable-find "bsfilter")
+ "File path of the Bsfilter executable program."
+ :type '(choice (file :tag "Location of bsfilter")
+ (const :tag "Bsfilter is not installed"))
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-header "X-Spam-Flag"
+ "The header inserted by Bsfilter to flag spam."
+ :type 'string
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-probability-header "X-Spam-Probability"
+ "The header that Bsfilter inserts in messages."
+ :type 'string
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-spam-switch "--add-spam"
+ "The switch that Bsfilter uses to register spam messages."
+ :type 'string
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-ham-switch "--add-ham"
+ "The switch that Bsfilter uses to register ham messages."
+ :type 'string
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-spam-strong-switch "--sub-spam"
+ "The switch that Bsfilter uses to unregister ham messages."
+ :type 'string
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-ham-strong-switch "--sub-clean"
+ "The switch that Bsfilter uses to unregister spam messages."
+ :type 'string
+ :group 'spam-bsfilter)
+
+(defcustom spam-bsfilter-database-directory nil
+ "Directory path of the Bsfilter databases."
+ :type '(choice (directory
+ :tag "Location of the Bsfilter database directory")
+ (const :tag "Use the default"))
+ :group 'spam-bsfilter)
+
(defgroup spam-spamoracle nil
"Spam spamoracle configuration."
:group 'spam)
:type 'string
:group 'spam-spamassassin)
+(defgroup spam-crm114 nil
+ "Spam CRM114 Mailfilter configuration."
+ :group 'spam)
+
+(defcustom spam-crm114-program (executable-find "mailfilter.crm")
+ "File path of the CRM114 Mailfilter executable program."
+ :type '(choice (file :tag "Location of CRM114 Mailfilter")
+ (const :tag "CRM114 Mailfilter is not installed"))
+ :group 'spam-crm114)
+
+(defcustom spam-crm114-header "X-CRM114-Status"
+ "The header that CRM114 Mailfilter inserts in messages."
+ :type 'string
+ :group 'spam-crm114)
+
+(defcustom spam-crm114-spam-switch "--learnspam"
+ "The switch that CRM114 Mailfilter uses to register spam messages."
+ :type 'string
+ :group 'spam-crm114)
+
+(defcustom spam-crm114-ham-switch "--learnnonspam"
+ "The switch that CRM114 Mailfilter uses to register ham messages."
+ :type 'string
+ :group 'spam-crm114)
+
+(defcustom spam-crm114-spam-strong-switch "--UNKNOWN"
+ "The switch that CRM114 Mailfilter uses to unregister ham messages."
+ :type 'string
+ :group 'spam-crm114)
+
+(defcustom spam-crm114-ham-strong-switch "--UNKNOWN"
+ "The switch that CRM114 Mailfilter uses to unregister spam messages."
+ :type 'string
+ :group 'spam-crm114)
+
+(defcustom spam-crm114-positive-spam-header "^SPAM"
+ "The regex on `spam-crm114-header' for positive spam identification."
+ :type 'regexp
+ :group 'spam-crm114)
+
+(defcustom spam-crm114-database-directory nil
+ "Directory path of the CRM114 Mailfilter databases."
+ :type '(choice (directory
+ :tag "Location of the CRM114 Mailfilter database directory")
+ (const :tag "Use the default"))
+ :group 'spam-crm114)
+
;;; Key bindings for spam control.
(gnus-define-keys gnus-summary-mode-map
:test 'equal)
"Cache of spam detection entries.")
-(defvar spam-old-ham-articles nil
- "List of old ham articles, generated when a group is entered.")
-
-(defvar spam-old-spam-articles nil
- "List of old spam articles, generated when a group is entered.")
+(defvar spam-old-articles nil
+ "List of old ham and spam articles, generated when a group is entered.")
(defvar spam-split-disabled nil
"If non-nil, `spam-split' is disabled, and always returns nil.")
`spam-split' will set this to nil or a spam-use-XYZ check if it
finds ham or spam.")
-;; convenience functions
+;; internal variables for backends
+;; TODO: find a way to create these on the fly in spam-install-backend-super
+(defvar spam-use-copy nil)
+(defvar spam-use-move nil)
+(defvar spam-use-gmane nil)
+(defvar spam-use-resend nil)
+
+;;}}}
+
+;;{{{ convenience functions
+
(defun spam-clear-cache (symbol)
"Clear the spam-caches entry for a check."
(remhash symbol spam-caches))
"Logical A xor B."
(and (or a b) (not (and a b))))
+(defun spam-set-difference (list1 list2)
+ "Return a set difference of LIST1 and LIST2.
+When either list is nil, the other is returned."
+ (if (and list1 list2)
+ ;; we have two non-nil lists
+ (progn
+ (dolist (item (append list1 list2))
+ (when (and (memq item list1) (memq item list2))
+ (setq list1 (delq item list1))
+ (setq list2 (delq item list2))))
+ (append list1 list2))
+ ;; if either of the lists was nil, return the other one
+ (if list1 list1 list2)))
+
(defun spam-group-ham-mark-p (group mark &optional spam)
"Checks if MARK is considered a ham mark in GROUP."
(when (stringp group)