Fix last change.
[elisp/flim.git] / smtp.el
diff --git a/smtp.el b/smtp.el
index c2c9937..2462c43 100644 (file)
--- a/smtp.el
+++ b/smtp.el
@@ -1,10 +1,11 @@
 ;;; 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>
+;;     Simon Leinen <simon@switch.ch> (ESMTP support)
+;;     Shuhei KOBAYASHI <shuhei@aqua.ocn.ne.jp>
+;;     Daiki Ueno <ueno@unixuser.org>
 ;; Keywords: SMTP, mail
 
 ;; 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 'cl))      ; push
+
+(require 'tram)
+
+(eval-and-compile
+  (luna-define-class smtp-stream (tram-stream)
+                    (process
+                     extensions))
+
+  (luna-define-internal-accessors 'smtp-stream))
 
 (defgroup smtp nil
   "SMTP protocol for sending mail."
   :group 'mail)
 
 (defcustom smtp-default-server nil
-  "*Specify default SMTP server."
+  "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)
 
 (defcustom smtp-use-8bitmime t
-  "*If non-nil, use ESMTP 8BITMIME if available."
+  "If non-nil, use ESMTP 8BITMIME if available."
   :type 'boolean
   :group 'smtp)
 
 (defcustom smtp-local-domain nil
-  "*Local domain name without a host name.
+  "Local domain name without a host name.
 If the function (system-name) returns the full internet address,
 don't define this value."
   :type '(choice (const nil) string)
   :group 'smtp)
 
-(defvar smtp-debug-info nil)
+(defcustom smtp-notify-success nil
+  "If non-nil, notification for successful mail delivery is returned 
+ to user (RFC1891)."
+  :type 'boolean
+  :group 'smtp)
+
+(defvar smtp-transaction-compose-function
+  #'smtp-default-transaction-compose-function)
+
+(defvar smtp-open-connection-function (function open-network-stream))
+
 (defvar smtp-read-point nil)
 
 (defun smtp-make-fqdn ()
@@ -76,159 +101,166 @@ don't define this value."
      (t
       (error "Cannot generate valid FQDN. Set `smtp-local-domain' correctly.")))))
 
+(defun smtp-greeting (trans)
+  (let ((response
+        (smtp-read-response
+         (smtp-stream-process-internal trans))))
+    (or (smtp-check-response response)
+       (tram-stream-error trans 'greeting))
+    trans))
+  
+(defun smtp-ehlo (trans)
+  (smtp-send-command
+   (smtp-stream-process-internal trans)
+   (format "EHLO %s" (smtp-make-fqdn)))
+  (let ((response
+        (smtp-read-response 
+         (smtp-stream-process-internal trans))))
+    (or (smtp-check-response response)
+       (tram-stream-error trans 'ehlo))
+    (smtp-stream-set-extensions-internal
+     trans (mapcar
+           (lambda (extension)
+             (car (read-from-string (downcase extension))))
+           (cdr response)))
+    trans))
+
+(defun smtp-helo (trans)
+  (smtp-send-command
+   (smtp-stream-process-internal trans)
+   (format "HELO %s" (smtp-make-fqdn)))
+  (let ((response
+        (smtp-read-response
+         (smtp-stream-process-internal trans))))
+    (or (smtp-check-response response)
+       (tram-stream-error trans 'helo))
+    trans))
+
+(defun smtp-mailfrom (sender trans)
+  (smtp-send-command
+   (smtp-stream-process-internal trans)
+   (format "MAIL FROM:<%s>%s"
+          sender
+          ;; SIZE --- Message Size Declaration (RFC1870)
+;;;       (if (memq 'size
+;;;                 (smtp-stream-extensions-internal trans))
+;;;           (format " SIZE=%d"
+;;;                   (save-excursion
+;;;                     (set-buffer buffer)
+;;;                     (+ (- (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)))
+;;;         "")
+          ;; 8BITMIME --- 8bit-MIMEtransport (RFC1652)
+          (if (and (memq '8bitmime
+                         (smtp-stream-extensions-internal trans))
+                   smtp-use-8bitmime)
+              " BODY=8BITMIME"
+            "")))
+  (let ((response
+        (smtp-read-response
+         (smtp-stream-process-internal trans))))
+    (or (smtp-check-response response)
+       (tram-stream-error trans 'mailfrom))
+    trans))
+
+(defun smtp-rcptto (recipient trans)
+  (let (response)
+    (smtp-send-command
+     (smtp-stream-process-internal trans)
+     (format
+      (if smtp-notify-success
+         "RCPT TO:<%s> NOTIFY=SUCCESS"
+       "RCPT TO:<%s>")
+      recipient))
+    (setq response
+         (smtp-read-response
+          (smtp-stream-process-internal trans)))
+    (or (smtp-check-response response)
+       (tram-stream-error trans 'rcptto))
+    trans))
+
+(defun smtp-data (buffer trans)
+  (smtp-send-command
+   (smtp-stream-process-internal trans)
+   "DATA")
+  (let ((response
+        (smtp-read-response
+         (smtp-stream-process-internal trans))))
+    (or (smtp-check-response response)
+       (tram-stream-error trans 'data))
+
+    ;; Mail contents
+    (smtp-send-data 
+     (smtp-stream-process-internal trans)
+     buffer)
+    ;; DATA end "."
+    (smtp-send-command
+     (smtp-stream-process-internal trans)
+     ".")
+    (setq response
+         (smtp-read-response
+          (smtp-stream-process-internal trans)))
+    (or (smtp-check-response response)
+       (tram-stream-error trans 'data))
+    trans))
+
+(defun smtp-default-transaction-compose-function (sender recipients buffer)
+  (tram-compose-transaction
+   `(&& smtp-greeting
+       (|| smtp-ehlo smtp-helo)
+       ,(closure-partial-call #'smtp-mailfrom sender)
+       ,@(mapcar
+          (lambda (recipient)
+            (closure-partial-call #'smtp-rcptto recipient))
+          recipients)
+       ,(closure-partial-call #'smtp-data buffer))))
+
 (defun smtp-via-smtp (sender recipients smtp-text-buffer)
   (let ((server (if (functionp smtp-server)
                    (funcall smtp-server sender recipients)
                  smtp-server))
-       process response extensions)
+       process response extensions trans error)
     (save-excursion
       (set-buffer
        (get-buffer-create
        (format "*trace of SMTP session to %s*" server)))
+      (buffer-disable-undo)
       (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))
-
-           (set-process-filter process 'smtp-process-filter)
-
-           ;; 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)))
-           (setq response (smtp-read-response process))
-           (if (or (null (car response))
-                   (not (integerp (car response)))
-                   (>= (car response) 400))
-               (progn
-                 ;; HELO
-                 (smtp-send-command process
-                                    (format "HELO %s" (smtp-make-fqdn)))
-                 (setq response (smtp-read-response process))
-                 (if (or (null (car response))
-                         (not (integerp (car response)))
-                         (>= (car response) 400))
-                     (throw 'done (car (cdr response)))))
-             (let ((extension-lines (cdr (cdr response))))
-               (while extension-lines
-                 (push (intern (downcase (substring (car extension-lines) 4)))
-                       extensions)
-                 (setq extension-lines (cdr extension-lines)))))
-
-           ;; 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))))))
-
-           ;; 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))))))
-
-           ;; 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))))))
-
-           ;; MAIL FROM:<sender>
-           (smtp-send-command
-            process
-            (format "MAIL FROM:<%s>%s%s"
-                    sender
-                    ;; SIZE --- Message Size Declaration (RFC1870)
-                    (if (memq 'size extensions)
-                        (format " SIZE=%d"
-                                (save-excursion
-                                  (set-buffer smtp-text-buffer)
-                                  (+ (- (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)))
-                      "")
-                    ;; 8BITMIME --- 8bit-MIMEtransport (RFC1652)
-                    (if (and (memq '8bitmime extensions)
-                             smtp-use-8bitmime)
-                        " BODY=8BITMIME"
-                      "")))
-           (setq response (smtp-read-response process))
-           (if (or (null (car response))
-                   (not (integerp (car response)))
-                   (>= (car response) 400))
-               (throw 'done (car (cdr response))))
-
-           ;; RCPT TO:<recipient>
-           (while recipients
-             (smtp-send-command process
-                                (format "RCPT TO:<%s>" (car recipients)))
-             (setq recipients (cdr recipients))
-             (setq response (smtp-read-response process))
-             (if (or (null (car response))
-                     (not (integerp (car response)))
-                     (>= (car response) 400))
-                 (throw 'done (car (cdr response)))))
-
-           ;; DATA
-           (smtp-send-command process "DATA")
-           (setq response (smtp-read-response process))
-           (if (or (null (car response))
-                   (not (integerp (car response)))
-                   (>= (car response) 400))
-               (throw 'done (car (cdr response))))
-
-           ;; Mail contents
-           (smtp-send-data process smtp-text-buffer)
-
-           ;; DATA end "."
-           (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))))
-
-           t)
-
-       (if (and process
-                (eq (process-status process) 'open))
-           (progn
-             ;; QUIT
-             (smtp-send-command process "QUIT")
-             (smtp-read-response process)
-             (delete-process process)))))))
+         (let ((function
+                (funcall smtp-transaction-compose-function
+                         sender recipients smtp-text-buffer)))
+           (or (functionp function)
+               (error "Unable to compose SMTP commands"))
+           (if (eq (car-safe function) 'lambda)
+               (setq function (byte-compile function)))
+           (as-binary-process
+            (setq process
+                  (funcall smtp-open-connection-function
+                           "SMTP" (current-buffer) server smtp-service)))
+           (when process
+             (set-process-filter process 'smtp-process-filter)
+             (setq trans
+                   (luna-make-entity 'smtp-stream :process process)
+                   error
+                   (catch (tram-stream-error-name trans)
+                     (funcall function trans)
+                     nil))
+             (not error)))
+       (when (and process
+                  (memq (process-status process) '(open run)))
+         ;; QUIT
+         (smtp-send-command process "QUIT")
+         (delete-process process))))))
 
 (defun smtp-process-filter (process output)
   (save-excursion
@@ -238,52 +270,28 @@ don't define this value."
 
 (defun smtp-read-response (process)
   (let ((case-fold-search nil)
-       (response-strings nil)
+       response
        (response-continue t)
-       (return-value '(nil ()))
        match-end)
-
     (while response-continue
       (goto-char smtp-read-point)
       (while (not (search-forward "\r\n" nil t))
        (accept-process-output process)
        (goto-char smtp-read-point))
-
       (setq match-end (point))
-      (setq response-strings
-           (cons (buffer-substring smtp-read-point (- match-end 2))
-                 response-strings))
-       
+      (setq response
+           (nconc response
+                  (list (buffer-substring (+ 4 smtp-read-point)
+                                          (- match-end 2)))))
       (goto-char smtp-read-point)
-      (if (looking-at "[0-9]+ ")
-         (let ((begin (match-beginning 0))
-               (end (match-end 0)))
-           (if smtp-debug-info
-               (message "%s" (car response-strings)))
-
-           (setq smtp-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-int
-                          (buffer-substring begin end))
-                         (nreverse response-strings)))))
-       
-       (if (looking-at "[0-9]+-")
-           (progn (if smtp-debug-info
-                    (message "%s" (car response-strings)))
-                  (setq smtp-read-point match-end)
-                  (setq response-continue t))
-         (progn
-           (setq smtp-read-point match-end)
-           (setq response-continue nil)
-           (setq return-value
-                 (cons nil (nreverse response-strings)))))))
-    (setq smtp-read-point match-end)
-    return-value))
+      (when (looking-at "[1-5][0-9][0-9] ")
+       (setq response-continue nil)
+       (push (read (point-marker)) response))
+      (setq smtp-read-point match-end))
+    response))
+
+(defun smtp-check-response (response)
+  (memq (/ (car response) 100) '(2 3)));; XXX
 
 (defun smtp-send-command (process command)
   (goto-char (point-max))
@@ -294,8 +302,6 @@ don't define this value."
 
 (defun smtp-send-data-1 (process data)
   (goto-char (point-max))
-  (if smtp-debug-info
-      (insert data "\r\n"))
   (setq smtp-read-point (point))
   ;; Escape "." at start of a line.
   (if (eq (string-to-char data) ?.)
@@ -329,8 +335,7 @@ don't define this value."
 
 (defun smtp-deduce-address-list (smtp-text-buffer header-start header-end)
   "Get address list suitable for smtp RCPT TO:<address>."
-  (let ((case-fold-search t)
-       (simple-address-list "")
+  (let ((simple-address-list "")
        this-line
        this-line-end
        addr-regexp
@@ -339,6 +344,7 @@ don't define this value."
        (save-excursion
          ;;
          (set-buffer smtp-address-buffer)
+         (setq case-fold-search t)
          (erase-buffer)
          (insert (save-excursion
                    (set-buffer smtp-text-buffer)