;;; rmail.el --- main code of "RMAIL" mail reader for Emacs
-;; Copyright (C) 1985,86,87,88,93,94,95,96,97,98,2000, 01, 2004
+;; Copyright (C) 1985,86,87,88,93,94,95,96,97,98,2000,01,2004,2005
;; Free Software Foundation, Inc.
;; Maintainer: FSF
:prefix "rmail-edit-"
:group 'rmail)
+(defgroup rmail-obsolete nil
+ "Rmail obsolete customization variables."
+ :group 'rmail)
(defcustom rmail-movemail-program nil
- "If non-nil, name of program for fetching new mail."
+ "If non-nil, the file name of the `movemail' program."
:group 'rmail-retrieve
:type '(choice (const nil) string))
(defcustom rmail-pop-password nil
- "*Password to use when reading mail from a POP server, if required."
+ "*Password to use when reading mail from POP server.
+Please use `rmail-remote-password' instead."
:type '(choice (string :tag "Password")
(const :tag "Not Required" nil))
- :group 'rmail-retrieve)
+ :group 'rmail-obsolete)
(defcustom rmail-pop-password-required nil
- "*Non-nil if a password is required when reading mail using POP."
+ "*Non-nil if a password is required when reading mail from a POP server.
+Please use rmail-remote-password-required instead."
:type 'boolean
- :group 'rmail-retrieve)
+ :group 'rmail-obsolete)
+
+(defcustom rmail-remote-password nil
+ "*Password to use when reading mail from a remote server.
+This setting is ignored for mailboxes whose URL already contains a password."
+ :type '(choice (string :tag "Password")
+ (const :tag "Not Required" nil))
+ :set-after '(rmail-pop-password)
+ :set #'(lambda (symbol value)
+ (set-default symbol
+ (if (and (not value)
+ (boundp 'rmail-pop-password)
+ rmail-pop-password)
+ rmail-pop-password
+ value))
+ (setq rmail-pop-password nil))
+ :group 'rmail-retrieve
+ :version "22.1")
+
+(defcustom rmail-remote-password-required nil
+ "*Non-nil if a password is required when reading mail from a remote server."
+ :type 'boolean
+ :set-after '(rmail-pop-password-required)
+ :set #'(lambda (symbol value)
+ (set-default symbol
+ (if (and (not value)
+ (boundp 'rmail-pop-password-required)
+ rmail-pop-password-required)
+ rmail-pop-password-required
+ value))
+ (setq rmail-pop-password-required nil))
+ :group 'rmail-retrieve
+ :version "22.1")
(defcustom rmail-movemail-flags nil
"*List of flags to pass to movemail.
:group 'rmail-retrieve
:version "20.3")
-(defvar rmail-pop-password-error "invalid usercode or password\\|
-unknown user name or bad password"
- "Regular expression matching incorrect-password POP server error messages.
+(defvar rmail-remote-password-error "invalid usercode or password\\|
+unknown user name or bad password\\|Authentication failed\\|MU_ERR_AUTH_FAILURE"
+ "Regular expression matching incorrect-password POP or IMAP server error
+messages.
If you get an incorrect-password error that this expression does not match,
please report it with \\[report-emacs-bug].")
-(defvar rmail-encoded-pop-password nil)
+(defvar rmail-encoded-remote-password nil)
(defcustom rmail-preserve-inbox nil
- "*Non-nil if incoming mail should be left in the user's inbox,
-rather than deleted, after it is retrieved."
+ "*Non-nil means leave incoming mail in the user's inbox--don't delete it."
:type 'boolean
:group 'rmail-retrieve)
+(defcustom rmail-movemail-search-path nil
+ "*List of directories to search for movemail (in addition to `exec-path')."
+ :group 'rmail-retrieve
+ :type '(repeat (directory)))
+
+(defun rmail-probe (prog)
+ "Determine what flavor of movemail PROG is.
+We do this by executing it with `--version' and analyzing its output."
+ (with-temp-buffer
+ (let ((tbuf (current-buffer)))
+ (buffer-disable-undo tbuf)
+ (call-process prog nil tbuf nil "--version")
+ (if (not (buffer-modified-p tbuf))
+ ;; Should not happen...
+ nil
+ (goto-char (point-min))
+ (cond
+ ((looking-at ".*movemail: invalid option")
+ 'emacs) ;; Possibly...
+ ((looking-at "movemail (GNU Mailutils .*)")
+ 'mailutils)
+ (t
+ ;; FIXME:
+ 'emacs))))))
+
+(defun rmail-autodetect ()
+ "Determine and return the file name of the `movemail' program.
+If `rmail-movemail-program' is non-nil, use it.
+Otherwise, look for `movemail' in the directories in
+`rmail-movemail-search-path', those in `exec-path', and `exec-directory'."
+ (if rmail-movemail-program
+ (rmail-probe rmail-movemail-program)
+ (catch 'scan
+ (dolist (dir (append rmail-movemail-search-path exec-path
+ (list exec-directory)))
+ (when (and dir (file-accessible-directory-p dir))
+ (let ((progname (expand-file-name "movemail" dir)))
+ (when (and (not (file-directory-p progname))
+ (file-executable-p progname))
+ (let ((x (rmail-probe progname)))
+ (when x
+ (setq rmail-movemail-program progname)
+ (throw 'scan x))))))))))
+
+(defvar rmail-movemail-variant-in-use nil
+ "The movemail variant currently in use. Known variants are:
+
+ `emacs' Means any implementation, compatible with the native Emacs one.
+ This is the default;
+ `mailutils' Means GNU mailutils implementation, capable of handling full
+mail URLs as the source mailbox;")
+
+;;;###autoload
+(defun rmail-movemail-variant-p (&rest variants)
+ "Return t if the current movemail variant is any of VARIANTS.
+Currently known variants are 'emacs and 'mailutils."
+ (when (not rmail-movemail-variant-in-use)
+ ;; Autodetect
+ (setq rmail-movemail-variant-in-use (rmail-autodetect)))
+ (not (null (member rmail-movemail-variant-in-use variants))))
+
;;;###autoload
(defcustom rmail-dont-reply-to-names nil "\
*A regexp specifying addresses to prune from a reply message.
;;;###autoload
(defcustom rmail-ignored-headers
- (concat "^via:\\|^mail-from:\\|^origin:\\|^references:"
+ (concat "^via:\\|^mail-from:\\|^origin:\\|^references:\\|^sender:"
"\\|^status:\\|^received:\\|^x400-originator:\\|^x400-recipients:"
"\\|^x400-received:\\|^x400-mts-identifier:\\|^x400-content-type:"
"\\|^\\(resent-\\|\\)message-id:\\|^summary-line:\\|^resent-date:"
"\\|^x-mailer:\\|^delivered-to:\\|^lines:\\|^mime-version:"
"\\|^content-transfer-encoding:\\|^x-coding-system:"
"\\|^return-path:\\|^errors-to:\\|^return-receipt-to:"
- "\\|^x-sign:\\|^x-beenthere:\\|^x-mailman-version:"
+ "\\|^x-sign:\\|^x-beenthere:\\|^x-mailman-version:\\|^x-mailman-copy:"
"\\|^precedence:\\|^list-help:\\|^list-post:\\|^list-subscribe:"
"\\|^list-id:\\|^list-unsubscribe:\\|^list-archive:"
"\\|^content-type:\\|^content-length:"
"\\|^x-attribution:\\|^x-disclaimer:\\|^x-trace:"
"\\|^x-complaints-to:\\|^nntp-posting-date:\\|^user-agent"
"\\|^importance:\\|^envelope-to:\\|^delivery-date"
- "\\|^x.*-priority:\\|^x-mimeole:")
+ "\\|^x.*-priority:\\|^x-mimeole:\\|^x-archive:"
+ "\\|^resent-face:\\|^resent-x.*:\\|^resent-organization\\|^resent-openpgp"
+ "\\|^openpgp:\\|^x-request-pgp:\\|^x-original.*:"
+ "\\|^x-virus-scanned:\\|^x-spam-[^s].*:")
"*Regexp to match header fields that Rmail should normally hide.
This variable is used for reformatting the message header,
which normally happens once for each message,
. font-lock-function-name-face)
'("^Reply-To:.*$" . font-lock-function-name-face)
'("^Subject:" . font-lock-comment-face)
+ '("^X-Spam-Status:" . font-lock-keyword-face)
'("^\\(To\\|Apparently-To\\|Cc\\|Newsgroups\\):"
. font-lock-keyword-face)
;; Use MATCH-ANCHORED to effectively anchor the regexp left side.
`(,cite-chars
(,(concat "\\=[ \t]*"
- "\\(\\([" cite-prefix "]+[" cite-suffix "]*\\)?"
- "\\(" cite-chars "[ \t]*\\)\\)+"
+ "\\(\\(\\([" cite-prefix "]+[" cite-suffix "]*\\)?"
+ "\\(" cite-chars "[ \t]*\\)\\)+\\)"
"\\(.*\\)")
(beginning-of-line) (end-of-line)
- (2 font-lock-constant-face nil t)
- (4 font-lock-comment-face nil t)))
+ (1 font-lock-comment-delimiter-face nil t)
+ (5 font-lock-comment-face nil t)))
'("^\\(X-[a-z0-9-]+\\|In-reply-to\\|Date\\):.*\\(\n[ \t]+.*\\)*$"
. font-lock-string-face))))
"Additional expressions to highlight in Rmail mode.")
(when rmail-display-summary
(rmail-summary))
(rmail-construct-io-menu))
- (run-hooks 'rmail-mode-hook)))
+ (run-mode-hooks 'rmail-mode-hook)))
(defun rmail-mode-2 ()
(kill-all-local-variables)
(if (and (featurep 'rmail-spam-filter)
rmail-use-spam-filter
(> rsf-number-of-spam 0))
- (progn (if rmail-spam-filter-beep (beep t))
- (sleep-for rmail-spam-sleep-after-message)))
+ (progn (if rsf-beep (beep t))
+ (sleep-for rsf-sleep-after-message)))
;; Move to the first new message
;; unless we have other unseen messages before it.
;; Don't leave the buffer screwed up if we get a disk-full error.
(or found (rmail-show-message)))))
+(defun rmail-parse-url (file)
+ "Parse the supplied URL. Return (list MAILBOX-NAME REMOTE PASSWORD GOT-PASSWORD)
+WHERE MAILBOX-NAME is the name of the mailbox suitable as argument to the
+actual version of `movemail', REMOTE is non-nil if MAILBOX-NAME refers to
+a remote mailbox, PASSWORD is the password if it should be
+supplied as a separate argument to `movemail' or nil otherwise, GOT-PASSWORD
+is non-nil if the user has supplied the password interactively.
+"
+ (cond
+ ((string-match "^\\([^:]+\\)://\\(\\([^:@]+\\)\\(:\\([^@]+\\)\\)?@\\)?.*" file)
+ (let (got-password supplied-password
+ (proto (match-string 1 file))
+ (user (match-string 3 file))
+ (pass (match-string 5 file))
+ (host (substring file (or (match-end 2)
+ (+ 3 (match-end 1))))))
+
+ (if (not pass)
+ (when rmail-remote-password-required
+ (setq got-password (not (rmail-have-password)))
+ (setq supplied-password (rmail-get-remote-password
+ (string-equal proto "imap")))))
+
+ (if (rmail-movemail-variant-p 'emacs)
+ (if (string-equal proto "pop")
+ (list (concat "po:" user ":" host)
+ t
+ (or pass supplied-password)
+ got-password)
+ (error "Emacs movemail does not support %s protocol" proto))
+ (list file
+ (or (string-equal proto "pop") (string-equal proto "imap"))
+ supplied-password
+ got-password))))
+
+ ((string-match "^po:\\([^:]+\\)\\(:\\(.*\\)\\)?" file)
+ (let (got-password supplied-password
+ (proto "pop")
+ (user (match-string 1 file))
+ (host (match-string 3 file)))
+
+ (when rmail-remote-password-required
+ (setq got-password (not (rmail-have-password)))
+ (setq supplied-password (rmail-get-remote-password nil)))
+
+ (list file "pop" supplied-password got-password)))
+
+ (t
+ (list file nil nil nil))))
+
(defun rmail-insert-inbox-text (files renamep)
;; Detect a locked file now, so that we avoid moving mail
;; out of the real inbox file. (That could scare people.)
(file-name-nondirectory buffer-file-name)))
(let (file tofile delete-files movemail popmail got-password password)
(while files
- ;; Handle POP mailbox names specially; don't expand as filenames
+ ;; Handle remote mailbox names specially; don't expand as filenames
;; in case the userid contains a directory separator.
(setq file (car files))
- (setq popmail (string-match "^po:" file))
+ (let ((url-data (rmail-parse-url file)))
+ (setq file (nth 0 url-data))
+ (setq popmail (nth 1 url-data))
+ (setq password (nth 2 url-data))
+ (setq got-password (nth 3 url-data)))
+
(if popmail
(setq renamep t)
(setq file (file-truename
(setq tofile (expand-file-name
;; Generate name to move to from inbox name,
;; in case of multiple inboxes that need moving.
- (concat ".newmail-" (file-name-nondirectory file))
+ (concat ".newmail-"
+ (file-name-nondirectory
+ (if (memq system-type '(windows-nt cygwin))
+ ;; cannot have "po:" in file name
+ (substring file 3)
+ file)))
;; Use the directory of this rmail file
;; because it's a nuisance to use the homedir
;; if that is on a full disk and this rmail
(expand-file-name buffer-file-name))))
;; Always use movemail to rename the file,
;; since there can be mailboxes in various directories.
- (setq movemail t)
-;;; ;; If getting from mail spool directory,
-;;; ;; use movemail to move rather than just renaming,
-;;; ;; so as to interlock with the mailer.
-;;; (setq movemail (string= file
-;;; (file-truename
-;;; (concat rmail-spool-directory
-;;; (file-name-nondirectory file)))))
- (if (and movemail (not popmail))
+ (if (not popmail)
(progn
;; On some systems, /usr/spool/mail/foo is a directory
;; and the actual inbox is /usr/spool/mail/foo/foo.
(setq file (expand-file-name (user-login-name)
file)))))
(cond (popmail
- (if rmail-pop-password-required
- (progn (setq got-password (not (rmail-have-password)))
- (setq password (rmail-get-pop-password))))
- (if (memq system-type '(windows-nt cygwin))
- ;; cannot have "po:" in file name
- (setq tofile
- (expand-file-name
- (concat ".newmail-pop-"
- (file-name-nondirectory (substring file 3)))
- (file-name-directory
- (expand-file-name buffer-file-name)))))
- (message "Getting mail from post office ..."))
+ (message "Getting mail from the remote server ..."))
((and (file-exists-p tofile)
(/= 0 (nth 7 (file-attributes tofile))))
(message "Getting mail from %s..." tofile))
((or (file-exists-p tofile) (and (not popmail)
(not (file-exists-p file))))
nil)
- ((and (not movemail) (not popmail))
- ;; Try copying. If that fails (perhaps no space) and
- ;; we're allowed to blow away the inbox, rename instead.
- (if rmail-preserve-inbox
- (copy-file file tofile nil)
- (condition-case nil
- (copy-file file tofile nil)
- (error
- ;; Third arg is t so we can replace existing file TOFILE.
- (rename-file file tofile t))))
- ;; Make the real inbox file empty.
- ;; Leaving it deleted could cause lossage
- ;; because mailers often won't create the file.
- (if (not rmail-preserve-inbox)
- (condition-case ()
- (write-region (point) (point) file)
- (file-error nil))))
(t
- (let ((errors nil))
- (unwind-protect
- (save-excursion
- (setq errors (generate-new-buffer " *rmail loss*"))
- (buffer-disable-undo errors)
- (let ((args
- (append
- (list (or rmail-movemail-program
- (expand-file-name "movemail"
- exec-directory))
- nil errors nil)
- (if rmail-preserve-inbox
- (list "-p")
- nil)
- rmail-movemail-flags
- (list file tofile)
- (if password (list password) nil))))
- (apply 'call-process args))
- (if (not (buffer-modified-p errors))
- ;; No output => movemail won
- nil
- (set-buffer errors)
- (subst-char-in-region (point-min) (point-max)
- ?\n ?\ )
- (goto-char (point-max))
- (skip-chars-backward " \t")
- (delete-region (point) (point-max))
- (goto-char (point-min))
- (if (looking-at "movemail: ")
- (delete-region (point-min) (match-end 0)))
- (beep t)
- (message "movemail: %s"
- (buffer-substring (point-min)
- (point-max)))
- ;; If we just read the password, most likely it is
- ;; wrong. Otherwise, see if there is a specific
- ;; reason to think that the problem is a wrong passwd.
- (if (or got-password
- (re-search-forward rmail-pop-password-error
- nil t))
- (rmail-set-pop-password nil))
- (sit-for 3)
- nil))
- (if errors (kill-buffer errors))))))
+ (with-temp-buffer
+ (let ((errors (current-buffer)))
+ (buffer-disable-undo errors)
+ (let ((args
+ (append
+ (list (or rmail-movemail-program
+ (expand-file-name "movemail"
+ exec-directory))
+ nil errors nil)
+ (if rmail-preserve-inbox
+ (list "-p")
+ nil)
+ (if (rmail-movemail-variant-p 'mailutils)
+ (append (list "--emacs") rmail-movemail-flags)
+ rmail-movemail-flags)
+ (list file tofile)
+ (if password (list password) nil))))
+ (apply 'call-process args))
+ (if (not (buffer-modified-p errors))
+ ;; No output => movemail won
+ nil
+ (set-buffer errors)
+ (subst-char-in-region (point-min) (point-max)
+ ?\n ?\ )
+ (goto-char (point-max))
+ (skip-chars-backward " \t")
+ (delete-region (point) (point-max))
+ (goto-char (point-min))
+ (if (looking-at "movemail: ")
+ (delete-region (point-min) (match-end 0)))
+ (beep t)
+ ;; If we just read the password, most likely it is
+ ;; wrong. Otherwise, see if there is a specific
+ ;; reason to think that the problem is a wrong passwd.
+ (if (or got-password
+ (re-search-forward rmail-remote-password-error
+ nil t))
+ (rmail-set-remote-password nil))
+
+ ;; If using Mailutils, remove initial error code
+ ;; abbreviation
+ (when (rmail-movemail-variant-p 'mailutils)
+ (goto-char (point-min))
+ (when (looking-at "[A-Z][A-Z0-9_]*:")
+ (delete-region (point-min) (match-end 0))))
+
+ (message "movemail: %s"
+ (buffer-substring (point-min)
+ (point-max)))
+
+ (sit-for 3)
+ nil)))))
+
;; At this point, TOFILE contains the name to read:
;; Either the alternate name (if we renamed)
;; or the actual inbox (if not renaming).
header-end t)
(let ((beg (point))
(eol (progn (end-of-line) (point))))
- (string-to-int (buffer-substring beg eol)))))))
+ (string-to-number (buffer-substring beg eol)))))))
(and size
(if (and (natnump size)
(<= (+ header-end size) (point-max))
(interactive)
(rmail-show-message rmail-current-message))
+(defun rmail-unknown-mail-followup-to ()
+ "Handle a \"Mail-Followup-To\" header field with an unknown mailing list.
+Ask the user whether to add that list name to `mail-mailing-lists'."
+ (save-restriction
+ (rmail-narrow-to-non-pruned-header)
+ (let ((mail-followup-to (mail-fetch-field "mail-followup-to" nil t)))
+ (when mail-followup-to
+ (let ((addresses
+ (split-string
+ (mail-strip-quoted-names mail-followup-to)
+ ",[[:space:]]+" t)))
+ (dolist (addr addresses)
+ (when (and (not (member addr mail-mailing-lists))
+ (not
+ ;; taken from rmailsum.el
+ (string-match
+ (or rmail-user-mail-address-regexp
+ (concat "^\\("
+ (regexp-quote (user-login-name))
+ "\\($\\|@\\)\\|"
+ (regexp-quote
+ (or user-mail-address
+ (concat (user-login-name) "@"
+ (or mail-host-address
+ (system-name)))))
+ "\\>\\)"))
+ addr))
+ (y-or-n-p
+ (format "Add `%s' to `mail-mailing-lists'? "
+ addr)))
+ (customize-save-variable 'mail-mailing-lists
+ (cons addr mail-mailing-lists)))))))))
+
(defun rmail-show-message (&optional n no-summary)
"Show message number N (prefix argument), counting from start of file.
If summary buffer is currently displayed, update current message there also."
(rmail-display-labels)
(if (eq rmail-enable-mime t)
(funcall rmail-show-mime-function)
- (setq rmail-view-buffer rmail-buffer)
- )
+ (setq rmail-view-buffer rmail-buffer))
+ (when mail-mailing-lists
+ (rmail-unknown-mail-followup-to))
(rmail-highlight-headers)
(if transient-mark-mode (deactivate-mark))
(run-hooks 'rmail-show-message-hook)
(progn (search-forward "\n*** EOOH ***\n")
(beginning-of-line) (point)))))
(setq from (mail-fetch-field "from")
- reply-to (or (mail-fetch-field "reply-to" nil t)
+ reply-to (or (if just-sender
+ (mail-fetch-field "mail-reply-to" nil t)
+ (mail-fetch-field "mail-followup-to" nil t))
+ (mail-fetch-field "reply-to" nil t)
from)
cc (and (not just-sender)
+ ;; mail-followup-to, if given, overrides cc.
+ (not (mail-fetch-field "mail-followup-to" nil t))
(mail-fetch-field "cc" nil t))
subject (mail-fetch-field "subject")
date (mail-fetch-field "date")
;; I don't know whether there are other mailers that still
;; need the names to be stripped.
;;; (mail-strip-quoted-names reply-to)
- reply-to
+ ;; Remove unwanted names from reply-to, since Mail-Followup-To
+ ;; header causes all the names in it to wind up in reply-to, not
+ ;; in cc. But if what's left is an empty list, use the original.
+ (let* ((reply-to-list (rmail-dont-reply-to reply-to)))
+ (if (string= reply-to-list "") reply-to reply-to-list))
subject
(rmail-make-in-reply-to-field from date message-id)
(if just-sender
(if (not from) (setq from user-mail-address))
(let ((tembuf (generate-new-buffer " sendmail temp"))
(case-fold-search nil)
+ (mail-personal-alias-file
+ (or mail-alias-file mail-personal-alias-file))
(mailbuf rmail-buffer))
(unwind-protect
(with-current-buffer tembuf
; nor is it meant to be.
;;;###autoload
-(defun rmail-set-pop-password (password)
- "Set PASSWORD to be used for retrieving mail from a POP server."
+(defun rmail-set-remote-password (password)
+ "Set PASSWORD to be used for retrieving mail from a POP or IMAP server."
(interactive "sPassword: ")
(if password
- (setq rmail-encoded-pop-password
+ (setq rmail-encoded-remote-password
(rmail-encode-string password (emacs-pid)))
- (setq rmail-pop-password nil)
- (setq rmail-encoded-pop-password nil)))
+ (setq rmail-remote-password nil)
+ (setq rmail-encoded-remote-password nil)))
-(defun rmail-get-pop-password ()
- "Get the password for retrieving mail from a POP server. If none
+(defun rmail-get-remote-password (imap)
+ "Get the password for retrieving mail from a POP or IMAP server. If none
has been set, then prompt the user for one."
- (if (not rmail-encoded-pop-password)
- (progn (if (not rmail-pop-password)
- (setq rmail-pop-password (read-passwd "POP password: ")))
- (rmail-set-pop-password rmail-pop-password)
- (setq rmail-pop-password nil)))
- (rmail-encode-string rmail-encoded-pop-password (emacs-pid)))
+ (when (not rmail-encoded-remote-password)
+ (if (not rmail-remote-password)
+ (setq rmail-remote-password
+ (read-passwd (if imap
+ "IMAP password: "
+ "POP password: "))))
+ (rmail-set-remote-password rmail-remote-password)
+ (setq rmail-remote-password nil))
+ (rmail-encode-string rmail-encoded-remote-password (emacs-pid)))
(defun rmail-have-password ()
- (or rmail-pop-password rmail-encoded-pop-password))
+ (or rmail-remote-password rmail-encoded-remote-password))
(defun rmail-encode-string (string mask)
"Encode STRING with integer MASK, by taking the exclusive OR of the