X-Git-Url: http://git.chise.org/gitweb/?a=blobdiff_plain;f=smtp.el;h=7674291d89b8d3425ba57d95558895a2b8c84b29;hb=e101a59a100d0d7a238aa4c3d85314d4e7dc3b93;hp=ab7a2fcbcd13d0fa8ce758686ba1b3a5dfde4b08;hpb=70a361ee801e23ab7c8c61d03275369c774a83b4;p=elisp%2Fflim.git diff --git a/smtp.el b/smtp.el index ab7a2fc..7674291 100644 --- a/smtp.el +++ b/smtp.el @@ -25,30 +25,40 @@ ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. + +;;; Commentary: +;; + ;;; Code: (require 'pces) (require 'pcustom) (require 'mail-utils) ; mail-strip-quoted-names +(require 'sasl) (defgroup smtp nil "SMTP protocol for sending mail." :group 'mail) +(defgroup smtp-extensions nil + "SMTP service extensions (RFC1869)." + :group 'smtp) + (defcustom smtp-default-server nil "Specify default SMTP server." :type '(choice (const nil) string) :group 'smtp) (defcustom smtp-server (or (getenv "SMTPSERVER") smtp-default-server) - "The name of the host running SMTP server. It can also be a function + "The name of the host running SMTP server. +It can also be a function called from `smtp-via-smtp' with arguments SENDER and RECIPIENTS." :type '(choice (string :tag "Name") (function :tag "Function")) :group 'smtp) (defcustom smtp-service "smtp" - "SMTP service port number. \"smtp\" or 25." + "SMTP service port number. \"smtp\" or 25." :type '(choice (integer :tag "25" 25) (string :tag "smtp" "smtp")) :group 'smtp) @@ -65,55 +75,139 @@ don't define this value." :type '(choice (const nil) string) :group 'smtp) -(defvar smtp-open-connection-function (function open-network-stream)) +(defcustom smtp-use-8bitmime t + "If non-nil, use ESMTP 8BITMIME (RFC1652) if available." + :type 'boolean + :group 'smtp-extensions) + +(defcustom smtp-use-size t + "If non-nil, use ESMTP SIZE (RFC1870) if available." + :type 'boolean + :group 'smtp-extensions) + +(defcustom smtp-use-starttls nil + "If non-nil, use STARTTLS (RFC2595) if available." + :type 'boolean + :group 'smtp-extensions) + +(defcustom smtp-use-sasl nil + "If non-nil, use SMTP Authentication (RFC2554) if available." + :type 'boolean + :group 'smtp-extensions) + +(defcustom smtp-sasl-user-name (user-login-name) + "Identification to be used for authorization." + :type 'string + :group 'smtp-extensions) + +(defcustom smtp-sasl-properties nil + "Properties set to SASL client." + :type 'string + :group 'smtp-extensions) + +(defcustom smtp-sasl-mechanisms nil + "List of authentication mechanisms." + :type '(repeat string) + :group 'smtp-extensions) + +(defvar sasl-mechanisms) + +(defvar smtp-open-connection-function #'open-network-stream) (defvar smtp-read-point nil) (defvar smtp-connection-alist nil) +(defvar smtp-submit-package-function #'smtp-submit-package) + ;;; @ SMTP package structure ;;; A package contains a mail message, an envelope sender address, ;;; and one or more envelope recipient addresses. In ESMTP model -;;; we should guarantee the hook methods to access the current sending package. +;;; the current sending package should be guaranteed to be accessible +;;; anywhere from the hook methods (or SMTP commands). -(defmacro smtp-package-sender-internal (package) +(defmacro smtp-package-sender (package) + "Return the sender of PACKAGE, a string." `(aref ,package 0)) -(defmacro smtp-package-recipients-internal (package) +(defmacro smtp-package-recipients (package) + "Return the recipients of PACKAGE, a list of strings." `(aref ,package 1)) -(defmacro smtp-package-buffer-internal (package) +(defmacro smtp-package-buffer (package) + "Return the data of PACKAGE, a buffer." `(aref ,package 2)) (defmacro smtp-make-package (sender recipients buffer) + "Create a new package structure. +A package is a unit of SMTP message +SENDER specifies the package sender, a string. +RECIPIENTS is a list of recipients. +BUFFER may be a buffer or a buffer name which contains mail message." `(vector ,sender ,recipients ,buffer)) +(defun smtp-package-buffer-size (package) + "Return the size of PACKAGE, an integer." + (save-excursion + (set-buffer (smtp-package-buffer package)) + (let ((size + (+ (buffer-size) + ;; 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))) + (goto-char (point-min)) + (while (re-search-forward "^\\." nil t) + (setq size (1+ size))) + size))) + ;;; @ SMTP connection structure -;;; We should take care of emulation for other network streams. -;;; They are likely to be implemented with sub program and the function -;;; `process-contact' returns process ID instead of `(HOST SERVICE)' pair. +;;; We should consider the function `open-network-stream' is a emulation +;;; for another network stream. They are likely to be implemented with an +;;; external program and the function `process-contact' returns the +;;; process id instead of `(HOST SERVICE)' pair. -(defmacro smtp-connection-process-internal (connection) +(defmacro smtp-connection-process (connection) + "Return the subprocess-object of CONNECTION." `(aref ,connection 0)) -(defmacro smtp-connection-server-internal (connection) +(defmacro smtp-connection-server (connection) + "Return the server of CONNECTION, a string." `(aref ,connection 1)) -(defmacro smtp-connection-service-internal (connection) +(defmacro smtp-connection-service (connection) + "Return the service of CONNECTION, a string or an integer." `(aref ,connection 2)) +(defmacro smtp-connection-extensions (connection) + "Return the SMTP extensions of CONNECTION, a list of strings." + `(aref ,connection 3)) + +(defmacro smtp-connection-set-extensions (connection extensions) + "Set the SMTP extensions of CONNECTION. +EXTENSIONS is a list of cons cells of the form \(EXTENSION . PARAMETERS). +Where EXTENSION is a symbol and PARAMETERS is a list of strings." + `(aset ,connection 3 ,extensions)) + (defmacro smtp-make-connection (process server service) - `(vector ,process ,server ,service)) + "Create a new connection structure. +PROCESS is an internal subprocess-object. SERVER is name of the host +to connect to. SERVICE is name of the service desired." + `(vector ,process ,server ,service nil)) (defun smtp-connection-opened (connection) "Say whether the CONNECTION to server has been opened." - (let ((process (smtp-connection-process-internal connection))) + (let ((process (smtp-connection-process connection))) (if (memq (process-status process) '(open run)) t))) (defun smtp-close-connection (connection) "Close the CONNECTION to server." - (let ((process (smtp-connection-process-internal connection))) + (let ((process (smtp-connection-process connection))) (delete-process process))) (defun smtp-make-fqdn () @@ -127,8 +221,7 @@ don't define this value." ((string-match "[^.]\\.[^.]" system-name) system-name) (t - (error "Cannot generate valid FQDN. Set `smtp-fqdn' \ -or `smtp-local-domain' correctly.")))))) + (error "Cannot generate valid FQDN")))))) (defun smtp-find-connection (buffer) "Find the connection delivering to BUFFER." @@ -142,7 +235,15 @@ or `smtp-local-domain' correctly.")))))) (delq entry smtp-connection-alist)) nil)))) +(eval-and-compile + (autoload 'starttls-open-stream "starttls") + (autoload 'starttls-negotiate "starttls")) + (defun smtp-open-connection (buffer server service) + "Open a SMTP connection for a service to a host. +Return a newly allocated connection-object. +BUFFER is the buffer to associate with the connection. SERVER is name +of the host to connect to. SERVICE is name of the service desired." (let ((process (as-binary-process (funcall smtp-open-connection-function @@ -158,12 +259,26 @@ or `smtp-local-domain' correctly.")))))) ;;;###autoload (defun smtp-via-smtp (sender recipients buffer) + (condition-case nil + (progn + (smtp-send-buffer sender recipients buffer) + t) + (smtp-error))) + +(make-obsolete 'smtp-via-smtp "It's old API.") + +;;;###autoload +(defun smtp-send-buffer (sender recipients buffer) (let ((server (if (functionp smtp-server) (funcall smtp-server sender recipients) smtp-server)) (package - (smtp-make-package sender recipients buffer))) + (smtp-make-package sender recipients buffer)) + (smtp-open-connection-function + (if smtp-use-starttls + #'starttls-open-stream + smtp-open-connection-function))) (save-excursion (set-buffer (get-buffer-create @@ -174,17 +289,20 @@ or `smtp-local-domain' correctly.")))))) (smtp-open-connection (current-buffer) server smtp-service)) (make-local-variable 'smtp-read-point) (setq smtp-read-point (point-min)) - (condition-case nil - (progn - (smtp-commit package) - t) - (smtp-response-error))))) + (funcall smtp-submit-package-function package)))) -(defun smtp-commit (package) +(defun smtp-submit-package (package) (unwind-protect (progn (smtp-primitive-greeting package) - (smtp-primitive-helo package) + (condition-case nil + (smtp-primitive-ehlo package) + (smtp-response-error + (smtp-primitive-helo package))) + (if smtp-use-starttls + (smtp-primitive-starttls package)) + (if smtp-use-sasl + (smtp-primitive-auth package)) (smtp-primitive-mailfrom package) (smtp-primitive-rcptto package) (smtp-primitive-data package)) @@ -194,36 +312,135 @@ or `smtp-local-domain' correctly.")))))) (smtp-primitive-quit package) (smtp-close-connection connection))))) -;;; @ hook methods for `smtp-commit' +;;; @ hook methods for `smtp-submit-package' ;;; + (defun smtp-primitive-greeting (package) (let* ((connection (smtp-find-connection (current-buffer))) (response (smtp-read-response - (smtp-connection-process-internal connection)))) + (smtp-connection-process connection)))) (if (/= (car response) 220) (smtp-response-error response)))) +(defun smtp-primitive-ehlo (package) + (let* ((connection + (smtp-find-connection (current-buffer))) + (process + (smtp-connection-process connection)) + response) + (smtp-send-command process (format "EHLO %s" (smtp-make-fqdn))) + (setq response (smtp-read-response process)) + (if (/= (car response) 250) + (smtp-response-error response)) + (smtp-connection-set-extensions + connection (mapcar + (lambda (extension) + (let ((extensions + (split-string extension))) + (setcar extensions + (car (read-from-string + (downcase (car extensions))))) + extensions)) + (cdr response))))) + (defun smtp-primitive-helo (package) (let* ((connection (smtp-find-connection (current-buffer))) (process - (smtp-connection-process-internal connection)) + (smtp-connection-process connection)) response) (smtp-send-command process (format "HELO %s" (smtp-make-fqdn))) (setq response (smtp-read-response process)) (if (/= (car response) 250) (smtp-response-error response)))) +(defun smtp-primitive-auth (package) + (let* ((connection + (smtp-find-connection (current-buffer))) + (process + (smtp-connection-process connection)) + (mechanisms + (cdr (assq 'auth (smtp-connection-extensions connection)))) + (sasl-mechanisms + (or smtp-sasl-mechanisms sasl-mechanisms)) + (mechanism + (sasl-find-mechanism mechanisms)) + client + name + step + response) + (unless mechanism + (error "No authentication mechanism available")) + (setq client (sasl-make-client mechanism smtp-sasl-user-name "smtp" + (smtp-connection-server connection))) + (if smtp-sasl-properties + (sasl-client-set-properties client smtp-sasl-properties)) + (setq name (sasl-mechanism-name mechanism) + ;; Retrieve the initial response + step (sasl-next-step client nil)) + (smtp-send-command + process + (if (sasl-step-data step) + (format "AUTH %s %s" name (base64-encode-string (sasl-step-data step) t)) + (format "AUTH %s" name))) + (catch 'done + (while t + (setq response (smtp-read-response process)) + (when (= (car response) 235) + ;; The authentication process is finished. + (setq step (sasl-next-step client step)) + (if (null step) + (throw 'done nil)) + (smtp-response-error response)) ;Bogus server? + (if (/= (car response) 334) + (smtp-response-error response)) + (sasl-step-set-data step (base64-decode-string (nth 1 response))) + (setq step (sasl-next-step client step)) + (smtp-send-command + process (if (sasl-step-data step) + (base64-encode-string (sasl-step-data step) t) + "")))))) + +(defun smtp-primitive-starttls (package) + (let* ((connection + (smtp-find-connection (current-buffer))) + (process + (smtp-connection-process connection)) + response) + ;; STARTTLS --- begin a TLS negotiation (RFC 2595) + (smtp-send-command process "STARTTLS") + (setq response (smtp-read-response process)) + (if (/= (car response) 220) + (smtp-response-error response)) + (starttls-negotiate process))) + (defun smtp-primitive-mailfrom (package) (let* ((connection (smtp-find-connection (current-buffer))) (process - (smtp-connection-process-internal connection)) + (smtp-connection-process connection)) + (extensions + (smtp-connection-extensions + connection)) + (sender + (smtp-package-sender package)) + extension response) + ;; SIZE --- Message Size Declaration (RFC1870) + (if (and smtp-use-size + (assq 'size extensions)) + (setq extension (format "SIZE=%d" (smtp-package-buffer-size package)))) + ;; 8BITMIME --- 8bit-MIMEtransport (RFC1652) + (if (and smtp-use-8bitmime + (assq '8bitmime extensions)) + (setq extension (concat extension " BODY=8BITMIME"))) (smtp-send-command - process (format "MAIL FROM:<%s>" (smtp-package-sender-internal package))) + process + (if extension + (format "MAIL FROM:<%s> %s" sender extension) + (format "MAIL FROM:<%s>" sender))) (setq response (smtp-read-response process)) (if (/= (car response) 250) (smtp-response-error response)))) @@ -232,29 +449,29 @@ or `smtp-local-domain' correctly.")))))) (let* ((connection (smtp-find-connection (current-buffer))) (process - (smtp-connection-process-internal connection)) + (smtp-connection-process connection)) (recipients - (smtp-package-recipients-internal package)) + (smtp-package-recipients package)) response) (while recipients (smtp-send-command - process (format "RCPT TO:<%s>" (pop recipients)))) - (setq response (smtp-read-response process)) - (unless (memq (car response) '(250 251)) - (smtp-response-error response)))) + process (format "RCPT TO:<%s>" (pop recipients))) + (setq response (smtp-read-response process)) + (unless (memq (car response) '(250 251)) + (smtp-response-error response))))) (defun smtp-primitive-data (package) (let* ((connection (smtp-find-connection (current-buffer))) (process - (smtp-connection-process-internal connection)) + (smtp-connection-process connection)) response) (smtp-send-command process "DATA") (setq response (smtp-read-response process)) (if (/= (car response) 354) (smtp-response-error response)) (save-excursion - (set-buffer (smtp-package-buffer-internal package)) + (set-buffer (smtp-package-buffer package)) (goto-char (point-min)) (while (not (eobp)) (smtp-send-data @@ -269,7 +486,7 @@ or `smtp-local-domain' correctly.")))))) (let* ((connection (smtp-find-connection (current-buffer))) (process - (smtp-connection-process-internal connection)) + (smtp-connection-process connection)) response) (smtp-send-command process "QUIT") (setq response (smtp-read-response process)) @@ -284,8 +501,11 @@ or `smtp-local-domain' correctly.")))))) (goto-char (point-max)) (insert output))) +(put 'smtp-error 'error-message "SMTP error") +(put 'smtp-error 'error-conditions '(smtp-error error)) + (put 'smtp-response-error 'error-message "SMTP response error") -(put 'smtp-response-error 'error-conditions '(smtp-response-error error)) +(put 'smtp-response-error 'error-conditions '(smtp-response-error smtp-error error)) (defun smtp-response-error (response) (signal 'smtp-response-error response)) @@ -307,9 +527,9 @@ or `smtp-local-domain' correctly.")))))) (goto-char (prog1 smtp-read-point (setq smtp-read-point (point)))) - (when (looking-at "[1-5][0-9][0-9] ") - (setq response-continue nil) - (push (read (point-marker)) response))) + (if (looking-at "[1-5][0-9][0-9] ") + (setq response (cons (read (point-marker)) response) + response-continue nil))) response)) (defun smtp-send-command (process command)