X-Git-Url: http://git.hcoop.net/bpt/emacs.git/blobdiff_plain/241094692158692abf6c958873d98e4738ad72ef..6c96b8933b2c5b5c85d8fa0c3f77431f5dae2e27:/lisp/mail/smtpmail.el diff --git a/lisp/mail/smtpmail.el b/lisp/mail/smtpmail.el index 17b02e3d6f..66c4bdd8df 100644 --- a/lisp/mail/smtpmail.el +++ b/lisp/mail/smtpmail.el @@ -1,6 +1,7 @@ ;;; smtpmail.el --- simple SMTP protocol (RFC 821) for sending mail -;; Copyright (C) 1995, 1996, 2001, 2002 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 2001, 2002, 2003, 2004, 2005, +;; 2006, 2007 Free Software Foundation, Inc. ;; Author: Tomoji Kagatani ;; Maintainer: Simon Josefsson @@ -25,8 +26,8 @@ ;; 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: @@ -44,6 +45,8 @@ ;; '(("YOUR SMTP HOST" 25 "username" "password"))) ;;(setq smtpmail-starttls-credentials ;; '(("YOUR SMTP HOST" 25 "~/.my_smtp_tls.key" "~/.my_smtp_tls.cert"))) +;; Where the 25 equals the value of `smtpmail-smtp-service', it can be an +;; integer or a string, just as long as they match (eq). ;; To queue mail, set smtpmail-queue-mail to t and use ;; smtpmail-send-queued-mail to send. @@ -96,13 +99,13 @@ This only has effect if you specify it before loading the smtpmail library." (defcustom smtpmail-smtp-service 25 "*SMTP service port number. -The default value would be \"smtp\" or 25 ." +The default value would be \"smtp\" or 25." :type '(choice (integer :tag "Port") (string :tag "Service")) :group 'smtpmail) (defcustom smtpmail-local-domain nil "*Local domain name without a host name. -If the function (system-name) returns the full internet address, +If the function `system-name' returns the full internet address, don't define this value." :type '(choice (const nil) string) :group 'smtpmail) @@ -142,7 +145,7 @@ The commands enables verbose information from the SMTP server." :group 'smtpmail) (defcustom smtpmail-queue-mail nil - "*Specify if mail is queued (if t) or sent immediately (if nil). + "*If set, mail is queued; otherwise it is sent immediately. If queued, it is stored in the directory `smtpmail-queue-dir' and sent with `smtpmail-send-queued-mail'." :type 'boolean @@ -167,13 +170,18 @@ looks like `user@realm'." (string :tag "Username") (choice (const :tag "Query when needed" nil) (string :tag "Password"))))) - :version "21.4" + :version "22.1" :group 'smtpmail) (defcustom smtpmail-starttls-credentials '(("" 25 "" "")) "Specify STARTTLS keys and certificates for servers. This is a list of four-element list with `servername' (a string), -`port' (an integer), `key' (a filename) and `certificate' (a filename)." +`port' (an integer), `key' (a filename) and `certificate' (a +filename). +If you do not have a certificate/key pair, leave the `key' and +`certificate' fields as `nil'. A key/certificate pair is only +needed if you want to use X.509 client authenticated +connections." :type '(repeat (list (string :tag "Server") (integer :tag "Port") (file :tag "Key") @@ -190,7 +198,7 @@ new SMTP extensions that might be useful to support." :group 'smtpmail) (defvar smtpmail-queue-index-file "index" - "File name of queued mail index, + "File name of queued mail index. This is relative to `smtpmail-queue-dir'.") (defvar smtpmail-address-buffer) @@ -204,7 +212,7 @@ This is relative to `smtpmail-queue-dir'.") (defvar smtpmail-queue-index (concat smtpmail-queue-dir smtpmail-queue-index-file)) -(defconst smtpmail-auth-supported '(cram-md5 login) +(defconst smtpmail-auth-supported '(cram-md5 plain login) "List of supported SMTP AUTH mechanisms.") ;;; @@ -212,7 +220,7 @@ This is relative to `smtpmail-queue-dir'.") ;;; (defvar smtpmail-mail-address nil - "Value of `user-mail-address' in ambient buffer.") + "Value to use for envelope-from address for mail from ambient buffer.") ;;;###autoload (defun smtpmail-send-it () @@ -223,7 +231,11 @@ This is relative to `smtpmail-queue-dir'.") (case-fold-search nil) delimline (mailbuf (current-buffer)) - (smtpmail-mail-address user-mail-address) + ;; Examine this variable now, so that + ;; local binding in the mail buffer will take effect. + (smtpmail-mail-address + (or (and mail-specify-envelope-from (mail-envelope-from)) + user-mail-address)) (smtpmail-code-conv-from (if enable-multibyte-characters (let ((sendmail-coding-system smtpmail-code-conv-from)) @@ -232,6 +244,11 @@ This is relative to `smtpmail-queue-dir'.") (save-excursion (set-buffer tembuf) (erase-buffer) + ;; Use the same buffer-file-coding-system as in the mail + ;; buffer, otherwise any write-region invocations (e.g., in + ;; mail-do-fcc below) will annoy with asking for a suitable + ;; encoding. + (set-buffer-file-coding-system smtpmail-code-conv-from nil t) (insert-buffer-substring mailbuf) (goto-char (point-max)) ;; require one newline at the end. @@ -314,6 +331,22 @@ This is relative to `smtpmail-queue-dir'.") (goto-char (point-min)) (unless (re-search-forward "^Date:" delimline t) (insert "Date: " (message-make-date) "\n")) + ;; Possibly add a MIME header for the current coding system + (let (charset) + (goto-char (point-min)) + (and (eq mail-send-nonascii 'mime) + (not (re-search-forward "^MIME-version:" delimline t)) + (progn (skip-chars-forward "\0-\177") + (/= (point) (point-max))) + smtpmail-code-conv-from + (setq charset + (coding-system-get smtpmail-code-conv-from + 'mime-charset)) + (goto-char delimline) + (insert "MIME-version: 1.0\n" + "Content-type: text/plain; charset=" + (symbol-name charset) + "\nContent-Transfer-Encoding: 8bit\n"))) ;; Insert an extra newline if we need it to work around ;; Sun's bug that swallows newlines. (goto-char (1+ delimline)) @@ -322,7 +355,10 @@ This is relative to `smtpmail-queue-dir'.") ;; Find and handle any FCC fields. (goto-char (point-min)) (if (re-search-forward "^FCC:" delimline t) - (mail-do-fcc delimline)) + ;; Force mail-do-fcc to use the encoding of the mail + ;; buffer to encode outgoing messages on FCC files. + (let ((coding-system-for-write smtpmail-code-conv-from)) + (mail-do-fcc delimline))) (if mail-interactive (with-current-buffer errbuf (erase-buffer)))) @@ -354,9 +390,12 @@ This is relative to `smtpmail-queue-dir'.") (buffer-data (create-file-buffer file-data)) (buffer-elisp (create-file-buffer file-elisp)) (buffer-scratch "*queue-mail*")) + (unless (file-exists-p smtpmail-queue-dir) + (make-directory smtpmail-queue-dir t)) (with-current-buffer buffer-data (erase-buffer) - (insert-buffer tembuf) + (set-buffer-file-coding-system smtpmail-code-conv-from nil t) + (insert-buffer-substring tembuf) (write-file file-data) (set-buffer buffer-elisp) (erase-buffer) @@ -387,7 +426,7 @@ This is relative to `smtpmail-queue-dir'.") ;;; mail, send it, etc... (let ((file-msg "")) (insert-file-contents smtpmail-queue-index) - (beginning-of-buffer) + (goto-char (point-min)) (while (not (eobp)) (setq file-msg (buffer-substring (point) (line-end-position))) (load file-msg) @@ -397,14 +436,17 @@ This is relative to `smtpmail-queue-dir'.") (with-temp-buffer (let ((coding-system-for-read 'no-conversion)) (insert-file-contents file-msg)) - (if (not (null smtpmail-recipient-address-list)) - (if (not (smtpmail-via-smtp smtpmail-recipient-address-list - (current-buffer))) - (error "Sending failed; SMTP protocol error")) - (error "Sending failed; no recipients"))) + (let ((smtpmail-mail-address + (or (and mail-specify-envelope-from (mail-envelope-from)) + user-mail-address))) + (if (not (null smtpmail-recipient-address-list)) + (if (not (smtpmail-via-smtp smtpmail-recipient-address-list + (current-buffer))) + (error "Sending failed; SMTP protocol error")) + (error "Sending failed; no recipients")))) (delete-file file-msg) (delete-file (concat file-msg ".el")) - (kill-line 1)) + (delete-region (point-at-bol) (point-at-bol 2))) (write-region (point-min) (point-max) smtpmail-queue-index)))) ;(defun smtpmail-via-smtp (host,port,sender,destination,smtpmail-text-buffer) @@ -453,23 +495,41 @@ This is relative to `smtpmail-queue-dir'.") (push el2 result))) (nreverse result))) +(defvar starttls-extra-args) +(defvar starttls-extra-arguments) + (defun smtpmail-open-stream (process-buffer host port) (let ((cred (smtpmail-find-credentials smtpmail-starttls-credentials host port))) (if (null (and cred (condition-case () - (call-process "starttls") + (with-no-warnings + (require 'starttls) + (call-process (if starttls-use-gnutls + starttls-gnutls-program + starttls-program))) (error nil)))) ;; The normal case. (open-network-stream "SMTP" process-buffer host port) (let* ((cred-key (smtpmail-cred-key cred)) (cred-cert (smtpmail-cred-cert cred)) (starttls-extra-args - (when (and (stringp cred-key) (stringp cred-cert) - (file-regular-p - (setq cred-key (expand-file-name cred-key))) - (file-regular-p - (setq cred-cert (expand-file-name cred-cert)))) - (list "--key-file" cred-key "--cert-file" cred-cert)))) + (append + starttls-extra-args + (when (and (stringp cred-key) (stringp cred-cert) + (file-regular-p + (setq cred-key (expand-file-name cred-key))) + (file-regular-p + (setq cred-cert (expand-file-name cred-cert)))) + (list "--key-file" cred-key "--cert-file" cred-cert)))) + (starttls-extra-arguments + (append + starttls-extra-arguments + (when (and (stringp cred-key) (stringp cred-cert) + (file-regular-p + (setq cred-key (expand-file-name cred-key))) + (file-regular-p + (setq cred-cert (expand-file-name cred-cert)))) + (list "--x509keyfile" cred-key "--x509certfile" cred-cert))))) (starttls-open-stream "SMTP" process-buffer host port))))) (defun smtpmail-try-auth-methods (process supported-extensions host port) @@ -477,9 +537,9 @@ This is relative to `smtpmail-queue-dir'.") (mech (car (smtpmail-intersection smtpmail-auth-supported mechs))) (cred (if (stringp smtpmail-auth-credentials) (let* ((netrc (netrc-parse smtpmail-auth-credentials)) - (hostentry (netrc-machine - netrc host (format "%s" (or port "smtp")) - "smtp"))) + (port-name (format "%s" (or port "smtp"))) + (hostentry (netrc-machine netrc host port-name + port-name))) (when hostentry (list host port (netrc-get hostentry "login") @@ -493,10 +553,10 @@ This is relative to `smtpmail-queue-dir'.") (smtpmail-cred-server cred) (smtpmail-cred-port cred)))))) ret) - (when cred + (when (and cred mech) (cond ((eq mech 'cram-md5) - (smtpmail-send-command process (format "AUTH %s" mech)) + (smtpmail-send-command process (upcase (format "AUTH %s" mech))) (if (or (null (car (setq ret (smtpmail-read-response process)))) (not (integerp (car ret))) (>= (car ret) 400)) @@ -506,7 +566,18 @@ This is relative to `smtpmail-queue-dir'.") (decoded (base64-decode-string challenge)) (hash (rfc2104-hash 'md5 64 16 passwd decoded)) (response (concat (smtpmail-cred-user cred) " " hash)) - (encoded (base64-encode-string response))) + ;; Osamu Yamane : + ;; SMTP auth fails because the SMTP server identifies + ;; only the first part of the string (delimited by + ;; new line characters) as a response from the + ;; client, and the rest as distinct commands. + + ;; In my case, the response string is 80 characters + ;; long. Without the no-line-break option for + ;; base64-encode-sting, only the first 76 characters + ;; are taken as a response to the server, and the + ;; authentication fails. + (encoded (base64-encode-string response t))) (smtpmail-send-command process (format "%s" encoded)) (if (or (null (car (setq ret (smtpmail-read-response process)))) (not (integerp (car ret))) @@ -519,18 +590,36 @@ This is relative to `smtpmail-queue-dir'.") (>= (car ret) 400)) (throw 'done nil)) (smtpmail-send-command - process (base64-encode-string (smtpmail-cred-user cred))) + process (base64-encode-string (smtpmail-cred-user cred) t)) (if (or (null (car (setq ret (smtpmail-read-response process)))) (not (integerp (car ret))) (>= (car ret) 400)) (throw 'done nil)) - (smtpmail-send-command process (base64-encode-string passwd)) + (smtpmail-send-command process (base64-encode-string passwd t)) (if (or (null (car (setq ret (smtpmail-read-response process)))) (not (integerp (car ret))) (>= (car ret) 400)) (throw 'done nil))) + ((eq mech 'plain) + ;; We used to send an empty initial request, and wait for an + ;; empty response, and then send the password, but this + ;; violate a SHOULD in RFC 2222 paragraph 5.1. Note that this + ;; is not sent if the server did not advertise AUTH PLAIN in + ;; the EHLO response. See RFC 2554 for more info. + (smtpmail-send-command process + (concat "AUTH PLAIN " + (base64-encode-string + (concat "\0" + (smtpmail-cred-user cred) + "\0" + passwd) t))) + (if (or (null (car (setq ret (smtpmail-read-response process)))) + (not (integerp (car ret))) + (not (equal (car ret) 235))) + (throw 'done nil))) + (t - (error "Mechanism %s not implemented" mech))) + (error "Mechanism %s not implemented" mech))) ;; Remember the password. (when (and (not (stringp smtpmail-auth-credentials)) (null (smtpmail-cred-passwd cred))) @@ -541,9 +630,12 @@ This is relative to `smtpmail-queue-dir'.") (host (or smtpmail-smtp-server (error "`smtpmail-smtp-server' not defined"))) (port smtpmail-smtp-service) - (envelope-from (or (mail-envelope-from) - smtpmail-mail-address - user-mail-address)) + ;; smtpmail-mail-address should be set to the appropriate + ;; buffer-local value by the caller, but in case not: + (envelope-from (or smtpmail-mail-address + (and mail-specify-envelope-from + (mail-envelope-from)) + user-mail-address)) response-code greeting process-buffer @@ -556,6 +648,7 @@ This is relative to `smtpmail-queue-dir'.") ;; clear the trace buffer of old output (with-current-buffer process-buffer + (setq buffer-undo-list t) (erase-buffer)) ;; open the connection to the server @@ -598,20 +691,22 @@ This is relative to `smtpmail-queue-dir'.") (>= (car response-code) 400)) (throw 'done nil))) (dolist (line (cdr (cdr response-code))) - (let ((name (mapcar (lambda (s) (intern (downcase s))) - (split-string (substring line 4) "[ ]")))) + (let ((name + (with-case-table ascii-case-table + (mapcar (lambda (s) (intern (downcase s))) + (split-string (substring line 4) "[ ]"))))) (and (eq (length name) 1) (setq name (car name))) - (and name + (and name (cond ((memq (if (consp name) (car name) name) '(verb xvrb 8bitmime onex xone - expn size dsn etrn - enhancedstatuscodes - help xusr - auth=login auth starttls)) - (setq supported-extensions - (cons name supported-extensions))) - (smtpmail-warn-about-unknown-extensions + expn size dsn etrn + enhancedstatuscodes + help xusr + auth=login auth starttls)) + (setq supported-extensions + (cons name supported-extensions))) + (smtpmail-warn-about-unknown-extensions (message "Unknown extension %s" name))))))) (if (and do-starttls @@ -657,7 +752,7 @@ This is relative to `smtpmail-queue-dir'.") (>= (car response-code) 400)) (throw 'done nil)))) - ;; MAIL FROM: + ;; MAIL FROM: (let ((size-part (if (or (member 'size supported-extensions) (assoc 'size supported-extensions)) @@ -666,13 +761,8 @@ This is relative to `smtpmail-queue-dir'.") ;; size estimate: (+ (- (point-max) (point-min)) ;; Add one byte for each change-of-line - ;; because or CR-LF representation: - (count-lines (point-min) (point-max)) - ;; For some reason, an empty line is - ;; added to the message. Maybe this - ;; is a bug, but it can't hurt to add - ;; those two bytes anyway: - 2))) + ;; because of CR-LF representation: + (count-lines (point-min) (point-max))))) "")) (body-part (if (member '8bitmime supported-extensions) @@ -692,21 +782,21 @@ This is relative to `smtpmail-queue-dir'.") "") ""))) ; (smtpmail-send-command process (format "MAIL FROM:%s@%s" (user-login-name) (smtpmail-fqdn))) - (smtpmail-send-command process (format "MAIL FROM: <%s>%s%s" - envelope-from + (smtpmail-send-command process (format "MAIL FROM:<%s>%s%s" + envelope-from size-part body-part)) - + (if (or (null (car (setq response-code (smtpmail-read-response process)))) (not (integerp (car response-code))) (>= (car response-code) 400)) (throw 'done nil) )) - ;; RCPT TO: + ;; RCPT TO: (let ((n 0)) (while (not (null (nth n recipient))) - (smtpmail-send-command process (format "RCPT TO: <%s>" (smtpmail-maybe-append-domain (nth n recipient)))) + (smtpmail-send-command process (format "RCPT TO:<%s>" (smtpmail-maybe-append-domain (nth n recipient)))) (setq n (1+ n)) (setq response-code (smtpmail-read-response process)) @@ -769,49 +859,49 @@ This is relative to `smtpmail-queue-dir'.") (response-continue t) (return-value '(nil ())) match-end) - - (while response-continue - (goto-char smtpmail-read-point) - (while (not (search-forward "\r\n" nil t)) - (accept-process-output process) - (goto-char smtpmail-read-point)) - - (setq match-end (point)) - (setq response-strings - (cons (buffer-substring smtpmail-read-point (- match-end 2)) - response-strings)) - - (goto-char smtpmail-read-point) - (if (looking-at "[0-9]+ ") - (let ((begin (match-beginning 0)) - (end (match-end 0))) - (if smtpmail-debug-info - (message "%s" (car response-strings))) - - (setq smtpmail-read-point match-end) - - ;; ignore lines that start with "0" - (if (looking-at "0[0-9]+ ") - nil + (catch 'done + (while response-continue + (goto-char smtpmail-read-point) + (while (not (search-forward "\r\n" nil t)) + (unless (memq (process-status process) '(open run)) + (throw 'done nil)) + (accept-process-output process) + (goto-char smtpmail-read-point)) + + (setq match-end (point)) + (setq response-strings + (cons (buffer-substring smtpmail-read-point (- match-end 2)) + response-strings)) + + (goto-char smtpmail-read-point) + (if (looking-at "[0-9]+ ") + (let ((begin (match-beginning 0)) + (end (match-end 0))) + (if smtpmail-debug-info + (message "%s" (car response-strings))) + + (setq smtpmail-read-point match-end) + + ;; ignore lines that start with "0" + (if (looking-at "0[0-9]+ ") + nil + (setq response-continue nil) + (setq return-value + (cons (string-to-number + (buffer-substring begin end)) + (nreverse response-strings))))) + + (if (looking-at "[0-9]+-") + (progn (if smtpmail-debug-info + (message "%s" (car response-strings))) + (setq smtpmail-read-point match-end) + (setq response-continue t)) + (progn + (setq smtpmail-read-point match-end) (setq response-continue nil) (setq return-value - (cons (string-to-int - (buffer-substring begin end)) - (nreverse response-strings))))) - - (if (looking-at "[0-9]+-") - (progn (if smtpmail-debug-info - (message "%s" (car response-strings))) - (setq smtpmail-read-point match-end) - (setq response-continue t)) - (progn - (setq smtpmail-read-point match-end) - (setq response-continue nil) - (setq return-value - (cons nil (nreverse response-strings))) - ) - ))) - (setq smtpmail-read-point match-end) + (cons nil (nreverse response-strings))))))) + (setq smtpmail-read-point match-end)) return-value)) @@ -844,31 +934,15 @@ This is relative to `smtpmail-queue-dir'.") ) (defun smtpmail-send-data (process buffer) - (let - ((data-continue t) - (sending-data nil) - this-line - this-line-end) - + (let ((data-continue t) sending-data) (with-current-buffer buffer (goto-char (point-min))) - (while data-continue (with-current-buffer buffer - (beginning-of-line) - (setq this-line (point)) - (end-of-line) - (setq this-line-end (point)) - (setq sending-data nil) - (setq sending-data (buffer-substring this-line this-line-end)) - (if (/= (forward-line 1) 0) - (setq data-continue nil))) - - (smtpmail-send-data-1 process sending-data) - ) - ) - ) - + (setq sending-data (buffer-substring (point-at-bol) (point-at-eol))) + (end-of-line 2) + (setq data-continue (not (eobp)))) + (smtpmail-send-data-1 process sending-data)))) (defun smtpmail-deduce-address-list (smtpmail-text-buffer header-start header-end) "Get address list suitable for smtp RCPT TO:
." @@ -946,4 +1020,5 @@ many continuation lines." (provide 'smtpmail) +;;; arch-tag: a76992df-6d71-43b7-9e72-4bacc6c05466 ;;; smtpmail.el ends here