;;; smtp.el --- basic functions to send mail with SMTP server
-;; Copyright (C) 1995, 1996, 1998, 1999 Free Software Foundation, Inc.
+;; Copyright (C) 1995, 1996, 1998, 1999, 2000 Free Software Foundation, Inc.
;; Author: Tomoji Kagatani <kagatani@rbc.ncl.omron.co.jp>
;; Simon Leinen <simon@switch.ch> (ESMTP support)
;;; Commentary:
-;;
+;;
;;; Code:
(require 'mail-utils) ; mail-strip-quoted-names
(require 'sasl)
(require 'luna)
+(require 'mel) ; binary-funcall
(defgroup smtp nil
"SMTP protocol for sending mail."
(function :tag "Function"))
:group 'smtp)
+(defcustom smtp-send-by-myself nil
+ "If non-nil, smtp.el send a mail by myself without smtp-server.
+This option requires \"dig.el\"."
+ :type 'boolean
+ :group 'smtp)
+
(defcustom smtp-service "smtp"
"SMTP service port number. \"smtp\" or 25."
:type '(choice (integer :tag "25" 25)
- (string :tag "smtp" "smtp"))
+ (string :tag "smtp" "smtp"))
:group 'smtp)
(defcustom smtp-local-domain nil
:type 'boolean
:group 'smtp-extensions)
+(defcustom smtp-use-starttls-ignore-error nil
+ "If non-nil, do not use STARTTLS if STARTTLS is not available."
+ :type 'boolean
+ :group 'smtp-extensions)
+
+(defcustom smtp-starttls-program "starttls"
+ "The program to run in a subprocess to open an TLSv1 connection."
+ :group 'smtp-extensions)
+
+(defcustom smtp-starttls-extra-args nil
+ "Extra arguments to `starttls-program'"
+ :group 'smtp-extensions)
+
(defcustom smtp-use-sasl nil
"If non-nil, use SMTP Authentication (RFC2554) if available."
:type 'boolean
(defvar sasl-mechanisms)
-(autoload 'binary-open-network-stream "raw-io")
;;;###autoload
-(defvar smtp-open-connection-function #'binary-open-network-stream)
+(defvar smtp-open-connection-function #'open-network-stream
+ "*Function used for connecting to a SMTP server.
+The function will be called with the same four arguments as
+`open-network-stream' and should return a process object.
+Here is an example:
+
+\(setq smtp-open-connection-function
+ #'(lambda (name buffer host service)
+ (let ((process-connection-type nil))
+ (start-process name buffer \"ssh\" \"-C\" host
+ \"nc\" host service))))
+
+It connects to a SMTP server using \"ssh\" before actually connecting
+to the SMTP port. Where the command \"nc\" is the netcat executable;
+see http://www.atstake.com/research/tools/index.html#network_utilities
+for details. In addition, you will have to modify the value for
+`smtp-end-of-line' to \"\\n\" if you use \"telnet\" instead of \"nc\".")
(defvar smtp-read-point nil)
(defvar smtp-submit-package-function #'smtp-submit-package)
+(defvar smtp-end-of-line "\r\n"
+ "*String to use as end-of-line marker when talking to a SMTP server.
+This is \"\\r\\n\" by default, but it may have to be \"\\n\" when using a non
+native connection function. See also `smtp-open-connection-function'.")
+
;;; @ SMTP package
;;; A package contains a mail message, an envelope sender address,
;;; and one or more envelope recipient addresses. In ESMTP model
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
- (funcall smtp-open-connection-function
- "SMTP" buffer server service))
+ (binary-funcall smtp-open-connection-function
+ "SMTP" buffer server service))
connection)
(when process
(setq connection (smtp-make-connection process server service))
smtp-connection-alist))
connection)))
+(eval-and-compile
+ (autoload 'dig-invoke "dig")
+ (autoload 'dig-extract-rr "dig"))
+
+(defun smtp-find-mx (domain &optional doerror)
+ (let (server)
+ ;; dig.el resolves only primally MX.
+ (cond ((setq server (smtp-dig domain "MX"))
+ (progn (string-match " \\([^ ]*\\)$" server)
+ (match-string 1 server)))
+ ((smtp-dig domain "A")
+ domain)
+ (t
+ (if doerror
+ (error (format "SMTP cannot resolve %s" domain)))))))
+
+(defun smtp-dig (domain type)
+ (let (dig-buf)
+ (set-buffer
+ (setq dig-buf (dig-invoke domain type)))
+ (prog1
+ (dig-extract-rr domain type)
+ (kill-buffer dig-buf))))
+
+(defun smtp-find-server (recipients)
+ (save-excursion
+ (let ((rec
+ (mapcar (lambda (recipient)
+ (let (server)
+ (if (and (string-match "@\\([^\t\n ]*\\)" recipient)
+ (setq server
+ (smtp-find-mx
+ (match-string 1 recipient))))
+ (cons server (list recipient))
+ (error (format "cannot find server for %s." recipient)))))
+ recipients))
+ ret rets rlist)
+ (while (setq rets (pop rec))
+ (if (setq ret (assoc (car rets) rec))
+ (setcdr ret
+ (append (cdr ret) (cdr rets)))
+ (setq rlist
+ (append rlist (list rets)))))
+ rlist)))
+
;;;###autoload
(defun smtp-via-smtp (sender recipients buffer)
+ "Like `smtp-send-buffer', but sucks in any errors."
(condition-case nil
(progn
(smtp-send-buffer sender recipients buffer)
;;;###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-open-connection-function
- (if smtp-use-starttls
- #'starttls-open-stream
- smtp-open-connection-function)))
- (save-excursion
- (set-buffer
- (get-buffer-create
- (format "*trace of SMTP session to %s*" server)))
- (erase-buffer)
- (buffer-disable-undo)
- (unless (smtp-find-connection (current-buffer))
- (smtp-open-connection (current-buffer) server smtp-service))
- (make-local-variable 'smtp-read-point)
- (setq smtp-read-point (point-min))
- (funcall smtp-submit-package-function package))))
+ "Send a message.
+SENDER is an envelope sender address.
+RECIPIENTS is a list of envelope recipient addresses.
+BUFFER may be a buffer or a buffer name which contains mail message."
+ (if smtp-send-by-myself
+ (smtp-send-buffer-by-myself sender recipients buffer)
+ (let* ((server
+ (if (functionp smtp-server)
+ (funcall smtp-server sender recipients)
+ (or smtp-server
+ (error "`smtp-server' not defined"))))
+ (package
+ (smtp-make-package sender recipients buffer))
+ (starttls-program smtp-starttls-program)
+ (starttls-extra-args smtp-starttls-extra-args)
+ (smtp-open-connection-function
+ (if smtp-use-starttls
+ #'starttls-open-stream
+ smtp-open-connection-function)))
+ (save-excursion
+ (set-buffer
+ (get-buffer-create
+ (format "*trace of SMTP session to %s*" server)))
+ (erase-buffer)
+ (buffer-disable-undo)
+ (unless (smtp-find-connection (current-buffer))
+ (smtp-open-connection (current-buffer) server smtp-service))
+ (make-local-variable 'smtp-read-point)
+ (setq smtp-read-point (point-min))
+ (funcall smtp-submit-package-function package)))))
(defun smtp-submit-package (package)
(unwind-protect
(smtp-response-error
(smtp-primitive-helo package)))
(if smtp-use-starttls
- (smtp-primitive-starttls package))
+ (if (assq 'starttls
+ (smtp-connection-extensions-internal
+ (smtp-find-connection (current-buffer))))
+ (progn
+ (smtp-primitive-starttls package)
+ (smtp-primitive-ehlo package))
+ (unless smtp-use-starttls-ignore-error
+ (error "STARTTLS is not supported on this server"))))
(if smtp-use-sasl
(smtp-primitive-auth package))
(smtp-primitive-mailfrom package)
(smtp-primitive-quit package)
(smtp-close-connection connection)))))
+(defun smtp-send-buffer-by-myself (sender recipients buffer)
+ "Send a message by myself.
+SENDER is an envelope sender address.
+RECIPIENTS is a list of envelope recipient addresses.
+BUFFER may be a buffer or a buffer name which contains mail message."
+ (let ((servers
+ (smtp-find-server recipients))
+ (smtp-open-connection-function
+ (if smtp-use-starttls
+ #'starttls-open-stream
+ smtp-open-connection-function))
+ server package)
+ (while (car servers)
+ (setq server (caar servers))
+ (setq recipients (cdar servers))
+ (if (not (and server recipients))
+ ;; MAILER-DAEMON is required. :)
+ (error (format "Cannot send <%s>"
+ (mapconcat 'concat recipients ">,<"))))
+ (setq package
+ (smtp-make-package sender recipients buffer))
+ (save-excursion
+ (set-buffer
+ (get-buffer-create
+ (format "*trace of SMTP session to %s*" server)))
+ (erase-buffer)
+ (buffer-disable-undo)
+ (unless (smtp-find-connection (current-buffer))
+ (smtp-open-connection (current-buffer) server smtp-service))
+ (make-local-variable 'smtp-read-point)
+ (setq smtp-read-point (point-min))
+ (let ((smtp-use-sasl nil)
+ (smtp-use-starttls-ignore-error t))
+ (funcall smtp-submit-package-function package)))
+ (setq servers (cdr servers)))))
+
;;; @ hook methods for `smtp-submit-package'
;;;
response)
(while response-continue
(goto-char smtp-read-point)
- (while (not (search-forward "\r\n" nil t))
+ (while (not (search-forward smtp-end-of-line nil t))
(accept-process-output (smtp-connection-process-internal connection))
(goto-char smtp-read-point))
(if decoder
(let ((string (buffer-substring smtp-read-point (- (point) 2))))
(delete-region smtp-read-point (point))
- (insert (funcall decoder string) "\r\n")))
+ (insert (funcall decoder string) smtp-end-of-line)))
(setq response
(nconc response
(list (buffer-substring
(smtp-connection-encoder-internal connection)))
(set-buffer (process-buffer process))
(goto-char (point-max))
- (setq command (concat command "\r\n"))
+ (setq command (concat command smtp-end-of-line))
(insert command)
(setq smtp-read-point (point))
(if encoder
(smtp-connection-encoder-internal connection)))
;; Escape "." at start of a line.
(if (eq (string-to-char data) ?.)
- (setq data (concat "." data "\r\n"))
- (setq data (concat data "\r\n")))
+ (setq data (concat "." data smtp-end-of-line))
+ (setq data (concat data smtp-end-of-line)))
(if encoder
(setq data (funcall encoder data)))
(process-send-string process data)))