fix
[elisp/flim.git] / smtp.el
diff --git a/smtp.el b/smtp.el
index c2c9937..e8fc8db 100644 (file)
--- a/smtp.el
+++ b/smtp.el
@@ -1,11 +1,14 @@
 ;;; smtp.el --- basic functions to send mail with SMTP server
 
-;; Copyright (C) 1995, 1996, 1998 Free Software Foundation, Inc.
+;; Copyright (C) 1995, 1996, 1998, 1999 Free Software Foundation, Inc.
 
 ;; Author: Tomoji Kagatani <kagatani@rbc.ncl.omron.co.jp>
-;;         Simon Leinen <simon@switch.ch> (ESMTP support)
-;;         Shuhei KOBAYASHI <shuhei-k@jaist.ac.jp>
-;; Keywords: SMTP, mail
+;;     Simon Leinen <simon@switch.ch> (ESMTP support)
+;;     Shuhei KOBAYASHI <shuhei@aqua.ocn.ne.jp>
+;;     Kenichi OKADA <okada@opaopa.org> (SASL support)
+;;     Daiki Ueno <ueno@ueda.info.waseda.ac.jp>
+;; Maintainer: Kenichi OKADA <okada@opaopa.org>
+;; Keywords: SMTP, mail, SASL
 
 ;; This file is part of FLIM (Faithful Library about Internet Message).
 
 ;; General Public License for more details.
 
 ;; 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
+;; along with this program; see the file COPYING.  If not, write to the
 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 ;; Boston, MA 02111-1307, USA.
 
 ;;; Code:
 
-(require 'mail-utils) ; pick up mail-strip-quoted-names
+(require 'poe)
+(require 'poem)
+(require 'pcustom)
+(require 'mail-utils)                  ; mail-strip-quoted-names
+
+(eval-when-compile (require 'sasl))
+(eval-and-compile
+  (autoload 'starttls-open-stream "starttls")
+  (autoload 'starttls-negotiate "starttls")
+  (autoload 'sasl-cram-md5 "sasl")
+  (autoload 'sasl-plain "sasl")
+  (autoload 'sasl-scram-md5-client-msg-1 "sasl")
+  (autoload 'sasl-scram-md5-client-msg-2 "sasl")
+  (autoload 'sasl-scram-md5-authenticate-server "sasl"))
+                      
+(eval-when-compile (require 'cl))      ; push
 
 (defgroup smtp nil
   "SMTP protocol for sending mail."
@@ -62,7 +80,38 @@ don't define this value."
   :type '(choice (const nil) string)
   :group 'smtp)
 
-(defvar smtp-debug-info nil)
+(defcustom smtp-debug-info nil
+  "*smtp debug info printout. messages and process buffer."
+  :type 'boolean
+  :group 'smtp)
+
+(defcustom smtp-notify-success nil
+  "*If non-nil, notification for successful mail delivery is returned 
+ to user (RFC1891)."
+  :type 'boolean
+  :group 'smtp)
+
+(defcustom smtp-authenticate-type nil
+  "*SMTP authentication mechanism (RFC2554)."
+  :type 'symbol
+  :group 'smtp)
+
+(defvar smtp-authenticate-user nil)
+(defvar smtp-authenticate-passphrase nil)
+
+(defvar smtp-authenticate-method-alist
+  '((cram-md5 smtp-auth-cram-md5)
+    (plain smtp-auth-plain)
+    (login smtp-auth-login)
+    (anonymous smtp-auth-anonymous)
+    (scram-md5 smtp-auth-scram-md5)
+    (digest-md5 smtp-auth-digest-md5)))
+
+(defcustom smtp-connection-type nil
+  "*SMTP connection type."
+  :type '(choice (const nil) (const :tag "TLS" starttls))
+  :group 'smtp)
+
 (defvar smtp-read-point nil)
 
 (defun smtp-make-fqdn ()
@@ -88,22 +137,29 @@ don't define this value."
       (erase-buffer)
       (make-local-variable 'smtp-read-point)
       (setq smtp-read-point (point-min))
-
+      
       (unwind-protect
          (catch 'done
-           (setq process (open-network-stream-as-binary
-                          "SMTP" (current-buffer) server smtp-service))
-           (or process (throw 'done nil))
-
+           (setq process 
+                 (if smtp-connection-type
+                     (as-binary-process
+                      (starttls-open-stream
+                       "SMTP" (current-buffer) server smtp-service))
+                   (open-network-stream-as-binary
+                    "SMTP" (current-buffer) server smtp-service)))
+           
            (set-process-filter process 'smtp-process-filter)
-
+           
+           (if (eq smtp-connection-type 'force)
+               (starttls-negotiate process))
+           
            ;; Greeting
            (setq response (smtp-read-response process))
            (if (or (null (car response))
                    (not (integerp (car response)))
                    (>= (car response) 400))
                (throw 'done (car (cdr response))))
-
+       
            ;; EHLO
            (smtp-send-command process
                               (format "EHLO %s" (smtp-make-fqdn)))
@@ -120,44 +176,72 @@ don't define this value."
                          (not (integerp (car response)))
                          (>= (car response) 400))
                      (throw 'done (car (cdr response)))))
-             (let ((extension-lines (cdr (cdr response))))
+             (let ((extension-lines (cdr (cdr response)))
+                   extension)
                (while extension-lines
-                 (push (intern (downcase (substring (car extension-lines) 4)))
-                       extensions)
+                 (if (string-match
+                      "^auth "
+                      (setq extension
+                            (downcase (substring (car extension-lines) 4))))
+                     (while (string-match "\\([^ ]+\\)" extension (match-end 1))
+                       (push (intern (match-string 1 extension)) extensions))
+                   (push (intern extension) extensions))
                  (setq extension-lines (cdr extension-lines)))))
+       
+           ;; STARTTLS --- begin a TLS negotiation (RFC 2595)
+           (when (and smtp-connection-type 
+                      (null (eq smtp-connection-type 'force))
+                      (memq 'starttls extensions))
+             (smtp-send-command process "STARTTLS")
+             (setq response (smtp-read-response process))
+             (if (or (null (car response))
+                     (not (integerp (car response)))
+                     (>= (car response) 400))
+                 (throw 'done (car (cdr response))))
+             (starttls-negotiate process))
+
+           ;; AUTH --- SMTP Service Extension for Authentication (RFC2554)
+           (when smtp-authenticate-type
+             (let ((auth (intern smtp-authenticate-type)) method)
+               (if (and 
+                    (memq auth extensions)
+                    (setq method (nth 1 (assq auth smtp-authenticate-method-alist))))
+                   (funcall method process)
+                 (throw 'smtp-error
+                        (format "AUTH mechanism %s not available" auth)))))
 
            ;; ONEX --- One message transaction only (sendmail extension?)
-           (if (or (memq 'onex extensions)
-                   (memq 'xone extensions))
-               (progn
-                 (smtp-send-command process "ONEX")
-                 (setq response (smtp-read-response process))
-                 (if (or (null (car response))
-                         (not (integerp (car response)))
-                         (>= (car response) 400))
-                     (throw 'done (car (cdr response))))))
+;;;        (if (or (memq 'onex extensions)
+;;;                (memq 'xone extensions))
+;;;            (progn
+;;;              (smtp-send-command process "ONEX")
+;;;              (setq response (smtp-read-response process))
+;;;              (if (or (null (car response))
+;;;                      (not (integerp (car response)))
+;;;                      (>= (car response) 400))
+;;;                  (throw 'done (car (cdr response))))))
 
            ;; VERB --- Verbose (sendmail extension?)
-           (if (and smtp-debug-info
-                    (or (memq 'verb extensions)
-                        (memq 'xvrb extensions)))
-               (progn
-                 (smtp-send-command process "VERB")
-                 (setq response (smtp-read-response process))
-                 (if (or (null (car response))
-                         (not (integerp (car response)))
-                         (>= (car response) 400))
-                     (throw 'done (car (cdr response))))))
+;;;        (if (and smtp-debug-info
+;;;                 (or (memq 'verb extensions)
+;;;                     (memq 'xvrb extensions)))
+;;;            (progn
+;;;              (smtp-send-command process "VERB")
+;;;              (setq response (smtp-read-response process))
+;;;              (if (or (null (car response))
+;;;                      (not (integerp (car response)))
+;;;                      (>= (car response) 400))
+;;;                  (throw 'done (car (cdr response))))))
 
            ;; XUSR --- Initial (user) submission (sendmail extension?)
-           (if (memq 'xusr extensions)
-               (progn
-                 (smtp-send-command process "XUSR")
-                 (setq response (smtp-read-response process))
-                 (if (or (null (car response))
-                         (not (integerp (car response)))
-                         (>= (car response) 400))
-                     (throw 'done (car (cdr response))))))
+;;;        (if (memq 'xusr extensions)
+;;;            (progn
+;;;              (smtp-send-command process "XUSR")
+;;;              (setq response (smtp-read-response process))
+;;;              (if (or (null (car response))
+;;;                      (not (integerp (car response)))
+;;;                      (>= (car response) 400))
+;;;                  (throw 'done (car (cdr response))))))
 
            ;; MAIL FROM:<sender>
            (smtp-send-command
@@ -193,7 +277,11 @@ don't define this value."
            ;; RCPT TO:<recipient>
            (while recipients
              (smtp-send-command process
-                                (format "RCPT TO:<%s>" (car recipients)))
+                                (format
+                                 (if smtp-notify-success
+                                     "RCPT TO:<%s> NOTIFY=SUCCESS" 
+                                   "RCPT TO:<%s>")
+                                 (car recipients)))
              (setq recipients (cdr recipients))
              (setq response (smtp-read-response process))
              (if (or (null (car response))
@@ -219,16 +307,16 @@ don't define this value."
                    (not (integerp (car response)))
                    (>= (car response) 400))
                (throw 'done (car (cdr response))))
-
+       
            t)
 
        (if (and process
                 (eq (process-status process) 'open))
-           (progn
-             ;; QUIT
-             (smtp-send-command process "QUIT")
-             (smtp-read-response process)
-             (delete-process process)))))))
+       (progn
+         ;; QUIT
+         (smtp-send-command process "QUIT")
+         (smtp-read-response process)
+         (delete-process process)))))))
 
 (defun smtp-process-filter (process output)
   (save-excursion
@@ -285,9 +373,11 @@ don't define this value."
     (setq smtp-read-point match-end)
     return-value))
 
-(defun smtp-send-command (process command)
+(defun smtp-send-command (process command &optional secure)
   (goto-char (point-max))
-  (insert command "\r\n")
+  (if secure
+      (insert "Here is insecure words.\r\n")
+    (insert command "\r\n"))
   (setq smtp-read-point (point))
   (process-send-string process command)
   (process-send-string process "\r\n"))
@@ -388,6 +478,216 @@ don't define this value."
            recipient-address-list))
       (kill-buffer smtp-address-buffer))))
 
+(defun smtp-auth-cram-md5 (process)
+  (let ((secure-word (copy-sequence smtp-authenticate-passphrase))
+       response)
+    (smtp-send-command process "AUTH CRAM-MD5")
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (throw 'done (car (cdr response))))
+    (smtp-send-command
+     process
+     (setq secure-word (unwind-protect
+                          (sasl-cram-md5
+                           smtp-authenticate-user secure-word
+                           (base64-decode-string
+                            (substring (car (cdr response)) 4)))
+                        (fillarray secure-word 0))
+          secure-word (unwind-protect
+                          (base64-encode-string secure-word)
+                        (fillarray secure-word 0))) t)
+    (fillarray secure-word 0)
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (throw 'done (car (cdr response))))))
+(defun smtp-auth-plain (process)
+  (let ((secure-word (copy-sequence smtp-authenticate-passphrase))
+       response)
+    (smtp-send-command
+     process
+     (setq secure-word (unwind-protect
+                          (sasl-plain "" smtp-authenticate-user secure-word)
+                        (fillarray secure-word 0))
+          secure-word (unwind-protect
+                          (base64-encode-string secure-word)
+                        (fillarray secure-word 0))
+          secure-word (unwind-protect
+                          (concat "AUTH PLAIN " secure-word)
+                        (fillarray secure-word 0))) t)
+    (fillarray secure-word 0)
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (throw 'done (car (cdr response))))))
+
+(defun smtp-auth-login (process)
+  (let ((secure-word (copy-sequence smtp-authenticate-passphrase))
+       response)
+    (smtp-send-command process "AUTH LOGIN")
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (throw 'done (car (cdr response))))
+    (smtp-send-command
+     process
+     (base64-encode-string
+      smtp-authenticate-user))
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (throw 'done (car (cdr response))))
+    (smtp-send-command
+     process
+     (setq secure-word (unwind-protect
+                          (base64-encode-string secure-word)
+                        (fillarray secure-word 0))) t)
+    (fillarray secure-word 0)
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (throw 'done (car (cdr response))))))
+
+(defun smtp-auth-anonymous (process &optional token)
+  (let (response)
+    (smtp-send-command
+     process "AUTH ANONYMOUS")
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (throw 'done (car (cdr response))))
+    (smtp-send-command process
+                      (base64-encode-string 
+                       (or token
+                           user-mail-address
+                           "")))
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (throw 'done (car (cdr response))))))
+(defun smtp-auth-scram-md5 (process)
+  ;; now tesing
+  (let (server-msg-1 server-msg-2 client-msg-1 salted-pass
+                    response secure-word)
+    (smtp-send-command process "AUTH SCRAM-MD5")
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (throw 'done (car (cdr response))))
+    (unwind-protect
+       (smtp-send-command
+        process
+        (setq secure-word
+              (base64-encode-string
+               (setq client-msg-1
+                     (sasl-scram-md5-client-msg-1 
+                      smtp-authenticate-user)))) t)
+      (fillarray secure-word 0))
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (progn
+         (fillarray client-msg-1 0)
+         (throw 'done (car (cdr response)))))
+    (setq secure-word
+         (unwind-protect
+             (substring (car (cdr response)) 4)
+           (fillarray (car (cdr response)) 0)))
+    (setq server-msg-1
+         (unwind-protect
+             (base64-decode-string secure-word)
+           (fillarray secure-word 0)))
+    (setq secure-word
+         (sasl-scram-md5-client-msg-2
+          server-msg-1 client-msg-1 
+          (setq salted-pass
+                (sasl-scram-md5-make-salted-pass
+                 smtp-authenticate-passphrase server-msg-1))))
+    (setq secure-word
+         (unwind-protect
+             (base64-encode-string secure-word)
+           (fillarray secure-word 0)))
+    (unwind-protect
+       (smtp-send-command process secure-word t)
+      (fillarray secure-word 0))
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (progn 
+         (fillarray salted-pass 0)
+         (fillarray server-msg-1 0)
+         (fillarray client-msg-1 0)
+         (throw 'done (car (cdr response)))))
+    (setq server-msg-2
+         (unwind-protect
+             (base64-decode-string
+              (setq secure-word
+                    (substring (car (cdr response)) 4)))
+           (fillarray secure-word 0)))
+    (if (null
+        (unwind-protect
+            (sasl-scram-md5-authenticate-server
+             server-msg-1
+             server-msg-2
+             client-msg-1
+             salted-pass)
+          (fillarray salted-pass 0)
+          (fillarray server-msg-1 0)
+          (fillarray server-msg-2 0)
+          (fillarray client-msg-1 0)))
+       (throw 'done nil))
+    (smtp-send-command process "")
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (throw 'done (car (cdr response)))) ))
+
+(defun smtp-auth-digest-md5 (process)
+  "Login to server using the AUTH DIGEST-MD5 method."
+  (let (user realm response)
+    (smtp-send-command process "AUTH DIGEST-MD5")
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (throw 'done (car (cdr response))))
+    (if (string-match "^\\([^@]*\\)@\\([^@]*\\)"
+                     smtp-authenticate-user)
+       (setq user (match-string 1 smtp-authenticate-user)
+             realm (match-string 2 smtp-authenticate-user))
+      (setq user smtp-authenticate-user
+           realm nil))
+    (smtp-send-command process
+                      (base64-encode-string
+                       (sasl-digest-md5-digest-response
+                        (base64-decode-string
+                         (substring (car (cdr response)) 4))
+                        user
+                        smtp-authenticate-passphrase
+                        "smtp" smtp-server realm)
+                       'no-line-break))
+    (setq response (smtp-read-response process))
+    (if (or (null (car response))
+           (not (integerp (car response)))
+           (>= (car response) 400))
+       (throw 'done (car (cdr response))))
+    (smtp-send-command process "")))
+    
 (provide 'smtp)
 
 ;;; smtp.el ends here