1 ;;; smtp.el --- basic functions to send mail with SMTP server
3 ;; Copyright (C) 1995, 1996, 1998, 1999, 2000 Free Software Foundation, Inc.
5 ;; Author: Tomoji Kagatani <kagatani@rbc.ncl.omron.co.jp>
6 ;; Simon Leinen <simon@switch.ch> (ESMTP support)
7 ;; Shuhei KOBAYASHI <shuhei@aqua.ocn.ne.jp>
8 ;; Daiki Ueno <ueno@unixuser.org>
9 ;; Keywords: SMTP, mail
11 ;; This file is part of FLIM (Faithful Library about Internet Message).
13 ;; This program is free software; you can redistribute it and/or
14 ;; modify it under the terms of the GNU General Public License as
15 ;; published by the Free Software Foundation; either version 2, or (at
16 ;; your option) any later version.
18 ;; This program is distributed in the hope that it will be useful, but
19 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 ;; General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with this program; see the file COPYING. If not, write to the
25 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26 ;; Boston, MA 02111-1307, USA.
35 (require 'mail-utils) ; mail-strip-quoted-names
38 (require 'mel) ; binary-funcall
41 "SMTP protocol for sending mail."
44 (defgroup smtp-extensions nil
45 "SMTP service extensions (RFC1869)."
48 (defcustom smtp-default-server nil
49 "Specify default SMTP server."
50 :type '(choice (const nil) string)
53 (defcustom smtp-server (or (getenv "SMTPSERVER") smtp-default-server)
54 "The name of the host running SMTP server.
55 It can also be a function
56 called from `smtp-via-smtp' with arguments SENDER and RECIPIENTS."
57 :type '(choice (string :tag "Name")
58 (function :tag "Function"))
61 (defcustom smtp-send-by-myself nil
62 "If non-nil, smtp.el send a mail by myself without smtp-server.
63 This option requires \"dig.el\"."
67 (defcustom smtp-service "smtp"
68 "SMTP service port number. \"smtp\" or 25."
69 :type '(choice (integer :tag "25" 25)
70 (string :tag "smtp" "smtp"))
73 (defcustom smtp-local-domain nil
74 "Local domain name without a host name.
75 If the function (system-name) returns the full internet address,
76 don't define this value."
77 :type '(choice (const nil) string)
80 (defcustom smtp-fqdn nil
81 "Fully qualified domain name used for Message-ID."
82 :type '(choice (const nil) string)
85 (defcustom smtp-use-8bitmime t
86 "If non-nil, use ESMTP 8BITMIME (RFC1652) if available."
88 :group 'smtp-extensions)
90 (defcustom smtp-use-size t
91 "If non-nil, use ESMTP SIZE (RFC1870) if available."
93 :group 'smtp-extensions)
95 (defcustom smtp-use-starttls nil
96 "If non-nil, use STARTTLS (RFC2595) if available."
98 :group 'smtp-extensions)
100 (defcustom smtp-use-starttls-ignore-error nil
101 "If non-nil, do not use STARTTLS if STARTTLS is not available."
103 :group 'smtp-extensions)
105 (defcustom smtp-use-sasl nil
106 "If non-nil, use SMTP Authentication (RFC2554) if available."
108 :group 'smtp-extensions)
110 (defcustom smtp-sasl-user-name (user-login-name)
111 "Identification to be used for authorization."
113 :group 'smtp-extensions)
115 (defcustom smtp-sasl-properties nil
116 "Properties set to SASL client."
118 :group 'smtp-extensions)
120 (defcustom smtp-sasl-mechanisms nil
121 "List of authentication mechanisms."
122 :type '(repeat string)
123 :group 'smtp-extensions)
125 (defvar sasl-mechanisms)
128 (defvar smtp-open-connection-function #'open-network-stream)
130 (defvar smtp-read-point nil)
132 (defvar smtp-connection-alist nil)
134 (defvar smtp-submit-package-function #'smtp-submit-package)
136 (defvar smtp-end-of-line "\r\n"
137 "*String to use on the end of lines when talking to the SMTP server.
138 This is \"\\r\\n\" by default, but should be \"\\n\" when using and
139 indirect connection method, e.g. bind `smtp-open-connection-function'
140 to a custom function as shown below:
142 \(setq smtp-open-connection-function
143 (lambda (name buffer host service)
144 (start-process name buffer \"ssh\" \"-C\" host
145 \"telnet\" \"-8\" host service)))")
148 ;;; A package contains a mail message, an envelope sender address,
149 ;;; and one or more envelope recipient addresses. In ESMTP model
150 ;;; the current sending package should be guaranteed to be accessible
151 ;;; anywhere from the hook methods (or SMTP commands).
154 (luna-define-class smtp-package ()
159 (luna-define-internal-accessors 'smtp-package))
161 (defun smtp-make-package (sender recipients buffer)
162 "Create a new package structure.
163 A package is a unit of SMTP message
164 SENDER specifies the package sender, a string.
165 RECIPIENTS is a list of recipients.
166 BUFFER may be a buffer or a buffer name which contains mail message."
167 (luna-make-entity 'smtp-package :sender sender :recipients recipients :buffer buffer))
169 (defun smtp-package-buffer-internal-size (package)
170 "Return the size of PACKAGE, an integer."
172 (set-buffer (smtp-package-buffer-internal package))
175 ;; Add one byte for each change-of-line
176 ;; because or CR-LF representation:
177 (count-lines (point-min) (point-max))
178 ;; For some reason, an empty line is
179 ;; added to the message. Maybe this
180 ;; is a bug, but it can't hurt to add
181 ;; those two bytes anyway:
183 (goto-char (point-min))
184 (while (re-search-forward "^\\." nil t)
185 (setq size (1+ size)))
188 ;;; @ SMTP connection
189 ;;; We should consider the function `open-network-stream' is a emulation
190 ;;; for another network stream. They are likely to be implemented with an
191 ;;; external program and the function `process-contact' returns the
192 ;;; process id instead of `(HOST SERVICE)' pair.
195 (luna-define-class smtp-connection ()
203 (luna-define-internal-accessors 'smtp-connection))
205 (defun smtp-make-connection (process server service)
206 "Create a new connection structure.
207 PROCESS is an internal subprocess-object. SERVER is name of the host
208 to connect to. SERVICE is name of the service desired."
209 (luna-make-entity 'smtp-connection :process process :server server :service service))
211 (luna-define-generic smtp-connection-opened (connection)
212 "Say whether the CONNECTION to server has been opened.")
214 (luna-define-generic smtp-close-connection (connection)
215 "Close the CONNECTION to server.")
217 (luna-define-method smtp-connection-opened ((connection smtp-connection))
218 (let ((process (smtp-connection-process-internal connection)))
219 (if (memq (process-status process) '(open run))
222 (luna-define-method smtp-close-connection ((connection smtp-connection))
223 (let ((process (smtp-connection-process-internal connection)))
224 (delete-process process)))
226 (defun smtp-make-fqdn ()
227 "Return user's fully qualified domain name."
230 (let ((system-name (system-name)))
233 (concat system-name "." smtp-local-domain))
234 ((string-match "[^.]\\.[^.]" system-name)
237 (error "Cannot generate valid FQDN"))))))
239 (defun smtp-find-connection (buffer)
240 "Find the connection delivering to BUFFER."
241 (let ((entry (assq buffer smtp-connection-alist))
244 (setq connection (nth 1 entry))
245 (if (smtp-connection-opened connection)
247 (setq smtp-connection-alist
248 (delq entry smtp-connection-alist))
252 (autoload 'starttls-open-stream "starttls")
253 (autoload 'starttls-negotiate "starttls"))
255 (defun smtp-open-connection (buffer server service)
256 "Open a SMTP connection for a service to a host.
257 Return a newly allocated connection-object.
258 BUFFER is the buffer to associate with the connection. SERVER is name
259 of the host to connect to. SERVICE is name of the service desired."
261 (binary-funcall smtp-open-connection-function
262 "SMTP" buffer server service))
265 (setq connection (smtp-make-connection process server service))
266 (set-process-filter process 'smtp-process-filter)
267 (setq smtp-connection-alist
268 (cons (list buffer connection)
269 smtp-connection-alist))
273 (autoload 'dig-invoke "dig")
274 (autoload 'dig-extract-rr "dig"))
276 (defun smtp-find-mx (domain &optional doerror)
278 ;; dig.el resolves only primally MX.
279 (cond ((setq server (smtp-dig domain "MX"))
280 (progn (string-match " \\([^ ]*\\)$" server)
281 (match-string 1 server)))
282 ((smtp-dig domain "A")
286 (error (format "SMTP cannot resolve %s" domain)))))))
288 (defun smtp-dig (domain type)
291 (setq dig-buf (dig-invoke domain type)))
293 (dig-extract-rr domain type)
294 (kill-buffer dig-buf))))
296 (defun smtp-find-server (recipients)
299 (mapcar (lambda (recipient)
301 (if (and (string-match "@\\([^\t\n ]*\\)" recipient)
304 (match-string 1 recipient))))
305 (cons server (list recipient))
306 (error (format "cannot find server for %s." recipient)))))
309 (while (setq rets (pop rec))
310 (if (setq ret (assoc (car rets) rec))
312 (append (cdr ret) (cdr rets)))
314 (append rlist (list rets)))))
318 (defun smtp-via-smtp (sender recipients buffer)
319 "Like `smtp-send-buffer', but sucks in any errors."
322 (smtp-send-buffer sender recipients buffer)
326 (make-obsolete 'smtp-via-smtp "It's old API.")
329 (defun smtp-send-buffer (sender recipients buffer)
331 SENDER is an envelope sender address.
332 RECIPIENTS is a list of envelope recipient addresses.
333 BUFFER may be a buffer or a buffer name which contains mail message."
334 (if smtp-send-by-myself
335 (smtp-send-buffer-by-myself sender recipients buffer)
337 (if (functionp smtp-server)
338 (funcall smtp-server sender recipients)
341 (smtp-make-package sender recipients buffer))
342 (smtp-open-connection-function
343 (if smtp-use-starttls
344 #'starttls-open-stream
345 smtp-open-connection-function)))
349 (format "*trace of SMTP session to %s*" server)))
351 (buffer-disable-undo)
352 (unless (smtp-find-connection (current-buffer))
353 (smtp-open-connection (current-buffer) server smtp-service))
354 (make-local-variable 'smtp-read-point)
355 (setq smtp-read-point (point-min))
356 (funcall smtp-submit-package-function package)))))
358 (defun smtp-submit-package (package)
361 (smtp-primitive-greeting package)
363 (smtp-primitive-ehlo package)
365 (smtp-primitive-helo package)))
366 (if smtp-use-starttls
368 (smtp-connection-extensions-internal
369 (smtp-find-connection (current-buffer))))
371 (smtp-primitive-starttls package)
372 (smtp-primitive-ehlo package))
373 (unless smtp-use-starttls-ignore-error
374 (error "STARTTLS is not supported on this server"))))
376 (smtp-primitive-auth package))
377 (smtp-primitive-mailfrom package)
378 (smtp-primitive-rcptto package)
379 (smtp-primitive-data package))
380 (let ((connection (smtp-find-connection (current-buffer))))
381 (when (smtp-connection-opened connection)
382 (smtp-primitive-quit package)
383 (smtp-close-connection connection)))))
385 (defun smtp-send-buffer-by-myself (sender recipients buffer)
386 "Send a message by myself.
387 SENDER is an envelope sender address.
388 RECIPIENTS is a list of envelope recipient addresses.
389 BUFFER may be a buffer or a buffer name which contains mail message."
391 (smtp-find-server recipients))
392 (smtp-open-connection-function
393 (if smtp-use-starttls
394 #'starttls-open-stream
395 smtp-open-connection-function))
398 (setq server (caar servers))
399 (setq recipients (cdar servers))
400 (if (not (and server recipients))
401 ;; MAILER-DAEMON is required. :)
402 (error (format "Cannot send <%s>"
403 (mapconcat 'concat recipients ">,<"))))
405 (smtp-make-package sender recipients buffer))
409 (format "*trace of SMTP session to %s*" server)))
411 (buffer-disable-undo)
412 (unless (smtp-find-connection (current-buffer))
413 (smtp-open-connection (current-buffer) server smtp-service))
414 (make-local-variable 'smtp-read-point)
415 (setq smtp-read-point (point-min))
416 (let ((smtp-use-sasl nil)
417 (smtp-use-starttls-ignore-error t))
418 (funcall smtp-submit-package-function package)))
419 (setq servers (cdr servers)))))
421 ;;; @ hook methods for `smtp-submit-package'
424 (defun smtp-primitive-greeting (package)
426 (smtp-find-connection (current-buffer)))
428 (smtp-read-response connection)))
429 (if (/= (car response) 220)
430 (smtp-response-error response))))
432 (defun smtp-primitive-ehlo (package)
434 (smtp-find-connection (current-buffer)))
436 (smtp-send-command connection (format "EHLO %s" (smtp-make-fqdn)))
437 (setq response (smtp-read-response connection))
438 (if (/= (car response) 250)
439 (smtp-response-error response))
440 (smtp-connection-set-extensions-internal
444 (split-string extension)))
446 (car (read-from-string
447 (downcase (car extensions)))))
451 (defun smtp-primitive-helo (package)
453 (smtp-find-connection (current-buffer)))
455 (smtp-send-command connection (format "HELO %s" (smtp-make-fqdn)))
456 (setq response (smtp-read-response connection))
457 (if (/= (car response) 250)
458 (smtp-response-error response))))
460 (defun smtp-primitive-auth (package)
462 (smtp-find-connection (current-buffer)))
464 (cdr (assq 'auth (smtp-connection-extensions-internal connection))))
466 (or smtp-sasl-mechanisms sasl-mechanisms))
468 (sasl-find-mechanism mechanisms))
474 (error "No authentication mechanism available"))
475 (setq client (sasl-make-client mechanism smtp-sasl-user-name "smtp"
476 (smtp-connection-server-internal connection)))
477 (if smtp-sasl-properties
478 (sasl-client-set-properties client smtp-sasl-properties))
479 (setq name (sasl-mechanism-name mechanism)
480 ;; Retrieve the initial response
481 step (sasl-next-step client nil))
484 (if (sasl-step-data step)
485 (format "AUTH %s %s" name (base64-encode-string (sasl-step-data step) t))
486 (format "AUTH %s" name)))
489 (setq response (smtp-read-response connection))
490 (when (= (car response) 235)
491 ;; The authentication process is finished.
492 (setq step (sasl-next-step client step))
495 (smtp-response-error response)) ;Bogus server?
496 (if (/= (car response) 334)
497 (smtp-response-error response))
498 (sasl-step-set-data step (base64-decode-string (nth 1 response)))
499 (setq step (sasl-next-step client step))
502 (if (sasl-step-data step)
503 (base64-encode-string (sasl-step-data step) t)
505 ;;; (smtp-connection-set-encoder-internal
506 ;;; connection (sasl-client-encoder client))
507 ;;; (smtp-connection-set-decoder-internal
508 ;;; connection (sasl-client-decoder client))
511 (defun smtp-primitive-starttls (package)
513 (smtp-find-connection (current-buffer)))
515 ;; STARTTLS --- begin a TLS negotiation (RFC 2595)
516 (smtp-send-command connection "STARTTLS")
517 (setq response (smtp-read-response connection))
518 (if (/= (car response) 220)
519 (smtp-response-error response))
520 (starttls-negotiate (smtp-connection-process-internal connection))))
522 (defun smtp-primitive-mailfrom (package)
524 (smtp-find-connection (current-buffer)))
526 (smtp-connection-extensions-internal
529 (smtp-package-sender-internal package))
532 ;; SIZE --- Message Size Declaration (RFC1870)
533 (if (and smtp-use-size
534 (assq 'size extensions))
535 (setq extension (format "SIZE=%d" (smtp-package-buffer-internal-size package))))
536 ;; 8BITMIME --- 8bit-MIMEtransport (RFC1652)
537 (if (and smtp-use-8bitmime
538 (assq '8bitmime extensions))
539 (setq extension (concat extension " BODY=8BITMIME")))
543 (format "MAIL FROM:<%s> %s" sender extension)
544 (format "MAIL FROM:<%s>" sender)))
545 (setq response (smtp-read-response connection))
546 (if (/= (car response) 250)
547 (smtp-response-error response))))
549 (defun smtp-primitive-rcptto (package)
551 (smtp-find-connection (current-buffer)))
553 (smtp-package-recipients-internal package))
557 connection (format "RCPT TO:<%s>" (pop recipients)))
558 (setq response (smtp-read-response connection))
559 (unless (memq (car response) '(250 251))
560 (smtp-response-error response)))))
562 (defun smtp-primitive-data (package)
564 (smtp-find-connection (current-buffer)))
566 (smtp-send-command connection "DATA")
567 (setq response (smtp-read-response connection))
568 (if (/= (car response) 354)
569 (smtp-response-error response))
571 (set-buffer (smtp-package-buffer-internal package))
572 (goto-char (point-min))
575 connection (buffer-substring (point) (progn (end-of-line)(point))))
576 (beginning-of-line 2)))
577 (smtp-send-command connection ".")
578 (setq response (smtp-read-response connection))
579 (if (/= (car response) 250)
580 (smtp-response-error response))))
582 (defun smtp-primitive-quit (package)
584 (smtp-find-connection (current-buffer)))
586 (smtp-send-command connection "QUIT")
587 (setq response (smtp-read-response connection))
588 (if (/= (car response) 221)
589 (smtp-response-error response))))
591 ;;; @ low level process manipulating function
593 (defun smtp-process-filter (process output)
595 (set-buffer (process-buffer process))
596 (goto-char (point-max))
599 (put 'smtp-error 'error-message "SMTP error")
600 (put 'smtp-error 'error-conditions '(smtp-error error))
602 (put 'smtp-response-error 'error-message "SMTP response error")
603 (put 'smtp-response-error 'error-conditions '(smtp-response-error smtp-error error))
605 (defun smtp-response-error (response)
606 (signal 'smtp-response-error response))
608 (defun smtp-read-response (connection)
610 (smtp-connection-decoder-internal connection))
611 (response-continue t)
613 (while response-continue
614 (goto-char smtp-read-point)
615 (while (not (search-forward smtp-end-of-line nil t))
616 (accept-process-output (smtp-connection-process-internal connection))
617 (goto-char smtp-read-point))
619 (let ((string (buffer-substring smtp-read-point (- (point) 2))))
620 (delete-region smtp-read-point (point))
621 (insert (funcall decoder string) smtp-end-of-line)))
624 (list (buffer-substring
625 (+ 4 smtp-read-point)
628 (prog1 smtp-read-point
629 (setq smtp-read-point (point))))
630 (if (looking-at "[1-5][0-9][0-9] ")
631 (setq response (cons (read (point-marker)) response)
632 response-continue nil)))
635 (defun smtp-send-command (connection command)
638 (smtp-connection-process-internal connection))
640 (smtp-connection-encoder-internal connection)))
641 (set-buffer (process-buffer process))
642 (goto-char (point-max))
643 (setq command (concat command smtp-end-of-line))
645 (setq smtp-read-point (point))
647 (setq command (funcall encoder command)))
648 (process-send-string process command))))
650 (defun smtp-send-data (connection data)
652 (smtp-connection-process-internal connection))
654 (smtp-connection-encoder-internal connection)))
655 ;; Escape "." at start of a line.
656 (if (eq (string-to-char data) ?.)
657 (setq data (concat "." data smtp-end-of-line))
658 (setq data (concat data smtp-end-of-line)))
660 (setq data (funcall encoder data)))
661 (process-send-string process data)))
663 (defun smtp-deduce-address-list (smtp-text-buffer header-start header-end)
664 "Get address list suitable for smtp RCPT TO:<address>."
665 (let ((simple-address-list "")
669 (smtp-address-buffer (generate-new-buffer " *smtp-mail*")))
673 (set-buffer smtp-address-buffer)
674 (setq case-fold-search t)
676 (insert (save-excursion
677 (set-buffer smtp-text-buffer)
678 (buffer-substring-no-properties header-start header-end)))
679 (goto-char (point-min))
680 ;; RESENT-* fields should stop processing of regular fields.
682 (if (re-search-forward "^RESENT-TO:" header-end t)
684 "^\\(RESENT-TO:\\|RESENT-CC:\\|RESENT-BCC:\\)")
685 (setq addr-regexp "^\\(TO:\\|CC:\\|BCC:\\)")))
687 (while (re-search-forward addr-regexp header-end t)
689 (setq this-line (match-beginning 0))
691 ;; get any continuation lines.
692 (while (and (looking-at "^[ \t]+") (< (point) header-end))
694 (setq this-line-end (point-marker))
695 (setq simple-address-list
696 (concat simple-address-list " "
697 (mail-strip-quoted-names
698 (buffer-substring this-line this-line-end)))))
701 (insert-string simple-address-list)
704 (subst-char-in-region (point-min) (point-max) 10 ? t)
706 (subst-char-in-region (point-min) (point-max) ?, ? t)
708 (subst-char-in-region (point-min) (point-max) 9 ? t)
710 (goto-char (point-min))
711 ;; tidyness in case hook is not robust when it looks at this
712 (while (re-search-forward "[ \t]+" header-end t) (replace-match " "))
714 (goto-char (point-min))
715 (let (recipient-address-list)
716 (while (re-search-forward " \\([^ ]+\\) " (point-max) t)
718 (setq recipient-address-list
719 (cons (buffer-substring (match-beginning 1) (match-end 1))
720 recipient-address-list)))
721 recipient-address-list))
722 (kill-buffer smtp-address-buffer))))
726 ;;; smtp.el ends here