123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- (require 'rmail)
- (require 'rmailsum)
- (defgroup rmail-spam-filter nil
- "Spam filter for Rmail, the Emacs mail reader."
- :group 'rmail)
- (defcustom rmail-use-spam-filter nil
- "Non-nil to activate the Rmail spam filter.
- Set `rsf-definitions-alist' to define what you consider spam emails."
- :type 'boolean
- :group 'rmail-spam-filter)
- (defcustom rsf-file "~/XRMAIL-SPAM"
- "Name of Rmail file for optionally saving some of the spam.
- You can either just delete spam, or save it in this file for
- later review. Which action to take for each spam definition is
- specified by the \"action\" element of the definition."
- :type 'string
- :group 'rmail-spam-filter)
- (defcustom rsf-no-blind-cc nil
- "Non-nil means mail with no explicit To: or Cc: is spam."
- :type 'boolean
- :group 'rmail-spam-filter)
- (defcustom rsf-ignore-case nil
- "Non-nil means to ignore case in `rsf-definitions-alist'."
- :type 'boolean
- :group 'rmail-spam-filter)
- (defcustom rsf-beep nil
- "Non-nil means to beep if spam is found."
- :type 'boolean
- :group 'rmail-spam-filter)
- (defcustom rsf-sleep-after-message 2.0
- "Seconds to wait after displaying a message that spam was found."
- :type 'number
- :group 'rmail-spam-filter)
- (defcustom rsf-min-region-to-spam-list 7
- "Minimum size of region that you can add to the spam list.
- The aim is to avoid adding too short a region, which could result
- in false positive identification of a valid message as spam."
- :type 'integer
- :group 'rmail-spam-filter)
- (defcustom rsf-autosave-newly-added-definitions nil
- "Non-nil to auto-save new spam entries.
- Any time you add an entry via the \"Spam\" menu, immediately saves
- the custom file."
- :type 'boolean
- :group 'rmail-spam-filter)
- (defcustom rsf-white-list nil
- "List of regexps to identify valid senders.
- If any element matches the \"From\" header, the message is
- flagged as a valid, non-spam message. E.g., if your domain is
- \"emacs.com\" then including \"emacs\\\\.com\" in this list would
- flag all mail (purporting to be) from your colleagues as valid."
- :type '(repeat string)
- :group 'rmail-spam-filter)
- (defcustom rsf-definitions-alist nil
- "A list of rules (definitions) matching spam messages.
- Each rule is an alist, with elements of the form (FIELD . REGEXP).
- The recognized FIELDS are: from, to, subject, content-type,
- x-spam-status, and contents. The \"contents\" element refers to
- the entire text of the message; all the other elements refer to
- message headers of the same name.
- Using an empty-string for REGEXP is the same as omitting that
- element altogether.
- Each rule should contain one \"action\" element, saying what to do
- if the rule is matched. This has the form (action . CHOICE), where
- CHOICE may be either `output-and-delete' (save to `rsf-file', then delete),
- or `delete-spam' (just delete).
- A rule matches only if all the specified elements match."
- :type '(repeat
- (list :format "%v"
- (cons :format "%v" :value (from . "")
- (const :format "" from)
- (string :tag "From" ""))
- (cons :format "%v" :value (to . "")
- (const :format "" to)
- (string :tag "To" ""))
- (cons :format "%v" :value (subject . "")
- (const :format "" subject)
- (string :tag "Subject" ""))
- (cons :format "%v" :value (content-type . "")
- (const :format "" content-type)
- (string :tag "Content-Type" ""))
- (cons :format "%v" :value (contents . "")
- (const :format "" contents)
- (string :tag "Contents" ""))
- (cons :format "%v" :value (x-spam-status . "")
- (const :format "" x-spam-status)
- (string :tag "X-Spam-Status" ""))
- (cons :format "%v" :value (action . output-and-delete)
- (const :format "" action)
- (choice :tag "Action selection"
- (const :tag "Output and delete" output-and-delete)
- (const :tag "Delete" delete-spam)
- ))))
- :group 'rmail-spam-filter)
- (defvar rsf-scanning-messages-now nil
- "Non-nil when `rmail-spam-filter' scans messages.")
- (defun rsf-check-field (field-symbol message-data definition result)
- "Check if a message appears to be spam.
- FIELD-SYMBOL is one of the possible keys of a `rsf-definitions-alist'
- rule; e.g. from, to. MESSAGE-DATA is a string giving the value of
- FIELD-SYMBOL in the current message. DEFINITION is the element of
- `rsf-definitions-alist' currently being checked.
- RESULT is a cons of the form (MAYBE-SPAM . IS-SPAM). If the car
- is nil, or if the entry for FIELD-SYMBOL in this DEFINITION is
- absent or the empty string, this function does nothing.
- Otherwise, if MESSAGE-DATA is non-nil and the entry matches it,
- the cdr is set to t. Else, the car is set to nil."
- (let ((definition-field (cdr (assoc field-symbol definition))))
-
- (if (and (car result) (> (length definition-field) 0))
-
-
- (if (and message-data
- (string-match definition-field message-data))
-
- (setcdr result t)
-
-
-
- (setcar result nil)))))
- (defun rmail-spam-filter (msg)
- "Return nil if message number MSG is spam based on `rsf-definitions-alist'.
- If spam, optionally output message to a file `rsf-file' and delete
- it from rmail file. Called for each new message retrieved by
- `rmail-get-new-mail'."
- (let ((return-value)
-
- (maybe-spam '(nil . nil))
- message-sender message-to message-cc message-recipients
- message-subject message-content-type message-spam-status
- (num-spam-definition-elements (safe-length rsf-definitions-alist))
- (num-element 0)
- (exit-while-loop nil)
-
- (case-fold-search rsf-ignore-case)
-
-
- (bbdb/mail_auto_create_p nil)
-
-
- (rsf-scanning-messages-now t))
- (save-excursion
-
-
- (save-restriction
- (goto-char (rmail-msgbeg msg))
- (narrow-to-region (point) (progn (search-forward "\n\n") (point)))
- (setq message-sender (mail-fetch-field "From"))
- (setq message-to (mail-fetch-field "To")
- message-cc (mail-fetch-field "Cc")
- message-recipients (or (and message-to message-cc
- (concat message-to ", " message-cc))
- message-to
- message-cc))
- (setq message-subject (mail-fetch-field "Subject"))
- (setq message-content-type (mail-fetch-field "Content-Type"))
- (setq message-spam-status (mail-fetch-field "X-Spam-Status")))
-
-
- (and rsf-no-blind-cc
- (null message-recipients)
- (setq exit-while-loop t
- maybe-spam '(t . t)))
-
- (and message-sender
- (let ((white-list rsf-white-list)
- (found nil))
- (while (and (not found) white-list)
- (if (string-match (car white-list) message-sender)
- (setq found t)
- (setq white-list (cdr white-list))))
- found)
- (setq exit-while-loop t
- maybe-spam '(nil . nil)))
-
- (while (and (< num-element num-spam-definition-elements)
- (not exit-while-loop))
- (let ((definition (nth num-element rsf-definitions-alist)))
-
-
-
-
-
-
-
-
-
-
- (setq maybe-spam (cons t nil))
-
-
-
-
-
-
- (rsf-check-field 'from message-sender definition maybe-spam)
-
- (rsf-check-field 'to message-recipients definition maybe-spam)
-
- (rsf-check-field 'subject message-subject definition maybe-spam)
-
- (rsf-check-field 'content-type message-content-type
- definition maybe-spam)
-
-
-
- (rsf-check-field 'contents
- (buffer-substring-no-properties
- (rmail-msgbeg msg) (rmail-msgend msg))
- definition maybe-spam)
-
-
- (rsf-check-field 'x-spam-status message-spam-status
- definition maybe-spam)
-
-
-
-
-
- (if (and (car maybe-spam) (cdr maybe-spam))
- (setq exit-while-loop t)
-
-
- (setq num-element (1+ num-element)))))
- (if (and (car maybe-spam) (cdr maybe-spam))
-
-
- (let ((rmail-current-message msg)
- (action (cdr (assq 'action
- (nth num-element rsf-definitions-alist))))
- (newfile (not (file-exists-p rsf-file))))
-
- (cond
- ((eq action 'output-and-delete)
-
-
- (and newfile
- (rmail-show-message (rmail-first-unseen-message) t))
- (rmail-output rsf-file)
-
- (when newfile
- (rmail-swap-buffers-maybe)
- (widen))
-
- (or rmail-delete-after-output (rmail-delete-message)))
- ((eq action 'delete-spam)
- (rmail-delete-message)))
- (setq return-value nil))
- (setq return-value t)))
- return-value))
- (defun rmail-get-new-mail-filter-spam (nnew)
- "Check the most NNEW recent messages for spam.
- This is called at the end of `rmail-get-new-mail-1' if there is new mail."
- (let* ((nold (- rmail-total-messages nnew))
- (nspam 0)
- (nscan (1+ nold))
-
- (rdv-old rmail-deleted-vector)
- errflag)
-
- (setq rmail-deleted-vector (make-string (1+ rmail-total-messages) ?\s))
- (while (and (not errflag) (<= nscan rmail-total-messages))
- (condition-case nil
- (or (rmail-spam-filter nscan)
- (setq nspam (1+ nspam)))
- (error (setq errflag nscan)))
- (setq nscan (1+ nscan)))
- (unwind-protect
- (if errflag
- (progn
- (setq rmail-use-spam-filter nil)
- (if rsf-beep (ding t))
- (message "Spam filter error for new message %d, disabled" errflag)
- (sleep-for rsf-sleep-after-message))
- (when (> nspam 0)
-
- (rmail-show-message (or (rmail-first-unseen-message) 1) t)
- (unwind-protect
- (progn
- (if rsf-beep (ding t))
- (message "Rmail spam-filter detected and deleted %d spam \
- message%s"
- nspam (if (= 1 nspam) "" "s"))
- (sleep-for rsf-sleep-after-message)
- (if (rmail-expunge-confirmed) (rmail-only-expunge t)))
-
- (rmail-swap-buffers-maybe)
- (widen))))
-
- (setq rmail-deleted-vector
- (concat (substring rdv-old 0 (1+ nold))
-
- (substring rmail-deleted-vector (1+ nold)))))
-
- (cond
- (errflag ", error in spam filter")
- ((zerop nspam) "")
- ((= 1 nnew) ", and it appears to be spam")
- ((= nspam nnew) ", and all appear to be spam")
- (t (format ", and %d appear%s to be spam" nspam
- (if (= 1 nspam) "s" ""))))))
- (defun rsf-add-subject-to-spam-list ()
- "Add the \"Subject\" header to the spam list."
- (interactive)
- (let ((message-subject (regexp-quote (rmail-get-header "Subject"))))
-
-
-
- (add-to-list 'rsf-definitions-alist
-
-
- (list '(from . "")
- '(to . "")
- `(subject . ,message-subject)
- '(content-type . "")
- '(contents . "")
- '(action . output-and-delete))
- t)
- (customize-mark-to-save 'rsf-definitions-alist)
- (if rsf-autosave-newly-added-definitions
- (progn
- (custom-save-all)
- (message "Added subject `%s' to spam list, and saved it"
- message-subject))
- (message "Added subject `%s' to spam list (remember to save it)"
- message-subject))))
- (defun rsf-add-sender-to-spam-list ()
- "Add the \"From\" address to the spam list."
- (interactive)
- (let ((message-sender (regexp-quote (rmail-get-header "From"))))
- (add-to-list 'rsf-definitions-alist
- (list `(from . ,message-sender)
- '(to . "")
- '(subject . "")
- '(content-type . "")
- '(contents . "")
- '(action . output-and-delete))
- t)
- (customize-mark-to-save 'rsf-definitions-alist)
- (if rsf-autosave-newly-added-definitions
- (progn
- (custom-save-all)
- (message "Added sender `%s' to spam list, and saved it"
- message-sender))
- (message "Added sender `%s' to spam list (remember to save it)"
- message-sender))))
- (defun rsf-add-region-to-spam-list ()
- "Add the marked region in the Rmail buffer to the spam list.
- Adds to spam definitions as a \"contents\" field."
- (interactive)
- (set-buffer rmail-buffer)
-
- (if (not (and mark-active (not (= (region-beginning) (region-end)))))
-
- (message "You must highlight some text in the Rmail buffer")
- (if (< (- (region-end) (region-beginning)) rsf-min-region-to-spam-list)
- (message "Region is too small (minimum %d characters)"
- rsf-min-region-to-spam-list)
-
- (let ((region-to-spam-list (regexp-quote
- (buffer-substring-no-properties
- (region-beginning) (region-end)))))
- (add-to-list 'rsf-definitions-alist
- (list '(from . "")
- '(to . "")
- '(subject . "")
- '(content-type . "")
- `(contents . ,region-to-spam-list)
- '(action . output-and-delete))
- t)
- (customize-mark-to-save 'rsf-definitions-alist)
- (if rsf-autosave-newly-added-definitions
- (progn
- (custom-save-all)
- (message "Added highlighted text:\n%s\n\
- to the spam list, and saved it" region-to-spam-list))
- (message "Added highlighted text:\n%s\n\
- to the spam list (remember to save it)" region-to-spam-list))))))
- (defun rsf-customize-spam-definitions ()
- "Customize `rsf-definitions-alist'."
- (interactive)
- (customize-variable 'rsf-definitions-alist))
- (defun rsf-customize-group ()
- "Customize the rmail-spam-filter group."
- (interactive)
- (customize-group 'rmail-spam-filter))
- (defun rsf-custom-save-all ()
- "Interactive version of `custom-save-all'."
- (interactive)
- (custom-save-all))
- (dolist (map (list rmail-summary-mode-map rmail-mode-map))
- (easy-menu-define nil map nil
- '("Spam"
- ["Add subject to spam list" rsf-add-subject-to-spam-list]
- ["Add sender to spam list" rsf-add-sender-to-spam-list]
- ["Add region to spam list" rsf-add-region-to-spam-list]
- ["Save spam definitions" rsf-custom-save-all]
- "--"
- ["Customize spam definitions" rsf-customize-spam-definitions]
- ["Browse spam customizations" rsf-customize-group]
- ))
- (define-key map "\C-cSt" 'rsf-add-subject-to-spam-list)
- (define-key map "\C-cSr" 'rsf-add-sender-to-spam-list)
- (define-key map "\C-cSn" 'rsf-add-region-to-spam-list)
- (define-key map "\C-cSa" 'rsf-custom-save-all)
- (define-key map "\C-cSd" 'rsf-customize-spam-definitions)
- (define-key map "\C-cSg" 'rsf-customize-group))
- (defun rsf-add-content-type-field ()
- "Maintain backward compatibility for `rmail-spam-filter'.
- The most recent version of `rmail-spam-filter' checks the content-type
- field of the incoming mail to see if it is spam. The format of
- `rsf-definitions-alist' has therefore changed. This function
- checks to see if the old format is used, and updates it if necessary."
- (interactive)
- (if (and rsf-definitions-alist
- (not (assoc 'content-type (car rsf-definitions-alist))))
- (let ((result nil)
- (current nil)
- (definitions rsf-definitions-alist))
- (while definitions
- (setq current (car definitions))
- (setq definitions (cdr definitions))
- (setq result
- (append result
- (list
- (list (assoc 'from current)
- (assoc 'to current)
- (assoc 'subject current)
- (cons 'content-type "")
- (assoc 'contents current)
- (assoc 'action current))))))
- (setq rsf-definitions-alist result)
- (customize-mark-to-save 'rsf-definitions-alist)
- (if rsf-autosave-newly-added-definitions
- (progn
- (custom-save-all)
- (message "Spam definitions converted to new format, and saved"))
- (message "Spam definitions converted to new format (remember to save)")))))
- (provide 'rmail-spam-filter)
|