* smtp.el (smtp-sasl-properties): New user option.
[elisp/flim.git] / smtp.el
diff --git a/smtp.el b/smtp.el
index c2fa2dd..7674291 100644 (file)
--- a/smtp.el
+++ b/smtp.el
 ;; 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 '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)
@@ -89,13 +95,13 @@ don't define this value."
   :type 'boolean
   :group 'smtp-extensions)
 
-(defcustom smtp-sasl-principal-name (user-login-name)
+(defcustom smtp-sasl-user-name (user-login-name)
   "Identification to be used for authorization."
   :type 'string
   :group 'smtp-extensions)
 
-(defcustom smtp-sasl-principal-realm smtp-local-domain
-  "Realm name to be used for authorization."
+(defcustom smtp-sasl-properties nil
+  "Properties set to SASL client."
   :type 'string
   :group 'smtp-extensions)
 
@@ -116,25 +122,34 @@ don't define this value."
 
 ;;; @ 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 user to access the current sending package
+;;; and one or more envelope recipient addresses.  In ESMTP model
+;;; 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-internal package))
+    (set-buffer (smtp-package-buffer package))
     (let ((size
           (+ (buffer-size)
              ;; Add one byte for each change-of-line
@@ -151,37 +166,48 @@ don't define this value."
       size)))
 
 ;;; @ SMTP connection structure
-;;; We should take care of a emulation for another network stream.
-;;; They are likely to be implemented with a external program and the function
-;;; `process-contact' returns the 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-internal (connection)
+(defmacro smtp-connection-extensions (connection)
+  "Return the SMTP extensions of CONNECTION, a list of strings."
   `(aref ,connection 3))
 
-(defmacro smtp-connection-set-extensions-internal (connection extensions)
+(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)
+  "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 ()
@@ -195,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."
@@ -215,6 +240,10 @@ or `smtp-local-domain' correctly."))))))
   (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
@@ -230,6 +259,16 @@ 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)
@@ -250,17 +289,16 @@ 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
-           (funcall smtp-submit-package-function package)
-           t)
-       (smtp-response-error)))))
+      (funcall smtp-submit-package-function 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
@@ -276,12 +314,13 @@ or `smtp-local-domain' correctly."))))))
 
 ;;; @ 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))))
 
@@ -289,13 +328,13 @@ 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 (format "EHLO %s" (smtp-make-fqdn)))
     (setq response (smtp-read-response process))
     (if (/= (car response) 250)
        (smtp-response-error response))
-    (smtp-connection-set-extensions-internal
+    (smtp-connection-set-extensions
      connection (mapcar
                 (lambda (extension)
                   (let ((extensions
@@ -310,69 +349,65 @@ 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 (format "HELO %s" (smtp-make-fqdn)))
     (setq response (smtp-read-response process))
     (if (/= (car response) 250)
        (smtp-response-error response))))
 
-(eval-and-compile
-  (autoload 'sasl-make-principal "sasl")
-  (autoload 'sasl-find-authenticator "sasl")
-  (autoload 'sasl-authenticator-mechanism-internal "sasl")
-  (autoload 'sasl-evaluate-challenge "sasl"))
-
 (defun smtp-primitive-auth (package)
   (let* ((connection
          (smtp-find-connection (current-buffer)))
         (process
-         (smtp-connection-process-internal connection))
+         (smtp-connection-process connection))
         (mechanisms
-         (cdr (assq 'auth (smtp-connection-extensions-internal connection))))
-        (principal
-         (sasl-make-principal
-          smtp-sasl-principal-name
-          "smtp" (smtp-connection-server-internal connection)
-          smtp-sasl-principal-realm))
-        (authenticator
-         (sasl-find-authenticator mechanisms))
+         (cdr (assq 'auth (smtp-connection-extensions connection))))
+        (sasl-mechanisms
+         (or smtp-sasl-mechanisms sasl-mechanisms))
         (mechanism
-         (sasl-authenticator-mechanism-internal authenticator))
-        ;; Retrieve the initial response
-        (sasl-response
-         (sasl-evaluate-challenge authenticator principal))
+         (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 (nth 1 sasl-response)
-        (format "AUTH %s %s" mechanism (base64-encode-string
-                                        (nth 1 sasl-response) t))
-       (format "AUTH %s" mechanism)))
+     (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 sasl-response
-               (sasl-evaluate-challenge authenticator principal sasl-response))
-         (if (null sasl-response)
+         (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))
-       (setcar (cdr sasl-response) (base64-decode-string (nth 1 response)))
-       (setq sasl-response
-             (sasl-evaluate-challenge
-              authenticator principal sasl-response))
-       (smtp-send-command process (base64-encode-string
-                                   (nth 1 sasl-response) t))))))
+       (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-internal connection))
+         (smtp-connection-process connection))
         response)
     ;; STARTTLS --- begin a TLS negotiation (RFC 2595)
     (smtp-send-command process "STARTTLS")
@@ -385,12 +420,12 @@ or `smtp-local-domain' correctly."))))))
   (let* ((connection
          (smtp-find-connection (current-buffer)))
         (process
-         (smtp-connection-process-internal connection))
+         (smtp-connection-process connection))
         (extensions
-         (smtp-connection-extensions-internal
+         (smtp-connection-extensions
           connection))
         (sender
-         (smtp-package-sender-internal package))
+         (smtp-package-sender package))
         extension
         response)
     ;; SIZE --- Message Size Declaration (RFC1870)
@@ -414,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
@@ -451,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))
@@ -466,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))