1 ;;; smtp.el --- basic functions to send mail with SMTP server
3 ;; Copyright (C) 1995, 1996, 1998, 1999 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 ;; Kenichi OKADA <okada@opaopa.org> (SASL support)
9 ;; Daiki Ueno <ueno@ueda.info.waseda.ac.jp>
10 ;; Maintainer: Kenichi OKADA <okada@opaopa.org>
11 ;; Keywords: SMTP, mail, SASL
13 ;; This file is part of FLIM (Faithful Library about Internet Message).
15 ;; This program is free software; you can redistribute it and/or
16 ;; modify it under the terms of the GNU General Public License as
17 ;; published by the Free Software Foundation; either version 2, or (at
18 ;; your option) any later version.
20 ;; This program is distributed in the hope that it will be useful, but
21 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 ;; General Public License for more details.
25 ;; You should have received a copy of the GNU General Public License
26 ;; along with this program; see the file COPYING. If not, write to the
27 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
28 ;; Boston, MA 02111-1307, USA.
35 (require 'mail-utils) ; mail-strip-quoted-names
37 (eval-when-compile (require 'sasl))
39 (autoload 'starttls-open-stream "starttls")
40 (autoload 'starttls-negotiate "starttls")
41 (autoload 'sasl-cram-md5 "sasl")
42 (autoload 'sasl-plain "sasl")
43 (autoload 'sasl-scram-md5-client-msg-1 "sasl")
44 (autoload 'sasl-scram-md5-client-msg-2 "sasl")
45 (autoload 'sasl-scram-md5-authenticate-server "sasl")
46 (autoload 'sasl-digest-md5-digest-response "sasl"))
48 (eval-when-compile (require 'cl)) ; push
51 "SMTP protocol for sending mail."
54 (defcustom smtp-default-server nil
55 "*Specify default SMTP server."
56 :type '(choice (const nil) string)
59 (defcustom smtp-server (or (getenv "SMTPSERVER") smtp-default-server)
60 "*The name of the host running SMTP server. It can also be a function
61 called from `smtp-via-smtp' with arguments SENDER and RECIPIENTS."
62 :type '(choice (string :tag "Name")
63 (function :tag "Function"))
66 (defcustom smtp-service "smtp"
67 "*SMTP service port number. \"smtp\" or 25."
68 :type '(choice (integer :tag "25" 25)
69 (string :tag "smtp" "smtp"))
72 (defcustom smtp-use-8bitmime t
73 "*If non-nil, use ESMTP 8BITMIME if available."
77 (defcustom smtp-local-domain nil
78 "*Local domain name without a host name.
79 If the function (system-name) returns the full internet address,
80 don't define this value."
81 :type '(choice (const nil) string)
84 (defcustom smtp-fqdn nil
85 "*User's fully qualified domain name."
86 :type '(choice (const nil) string)
89 (defcustom smtp-debug-info nil
90 "*smtp debug info printout. messages and process buffer."
94 (defcustom smtp-notify-success nil
95 "*If non-nil, notification for successful mail delivery is returned
100 (defcustom smtp-authenticate-type nil
101 "*SMTP authentication mechanism (RFC2554)."
105 (defvar smtp-authenticate-user nil)
106 (defvar smtp-authenticate-passphrase nil)
108 (defvar smtp-authenticate-method-alist
109 '((cram-md5 smtp-auth-cram-md5)
110 (plain smtp-auth-plain)
111 (login smtp-auth-login)
112 (anonymous smtp-auth-anonymous)
113 (scram-md5 smtp-auth-scram-md5)
114 (digest-md5 smtp-auth-digest-md5)))
116 (defcustom smtp-connection-type nil
117 "*SMTP connection type."
118 :type '(choice (const nil) (const :tag "TLS" starttls))
121 (defvar smtp-read-point nil)
123 (defun smtp-make-fqdn ()
124 "Return user's fully qualified domain name."
125 (let ((system-name (system-name)))
130 (concat system-name "." smtp-local-domain))
131 ((string-match "[^.]\\.[^.]" system-name)
134 (error "Cannot generate valid FQDN. Set `smtp-fqdn' or `smtp-local-domain' correctly.")))))
136 (defun smtp-via-smtp (sender recipients smtp-text-buffer)
137 (let ((server (if (functionp smtp-server)
138 (funcall smtp-server sender recipients)
140 process response extensions)
144 (format "*trace of SMTP session to %s*" server)))
146 (make-local-variable 'smtp-read-point)
147 (setq smtp-read-point (point-min))
152 (if smtp-connection-type
154 (starttls-open-stream
155 "SMTP" (current-buffer) server smtp-service))
156 (open-network-stream-as-binary
157 "SMTP" (current-buffer) server smtp-service)))
159 (set-process-filter process 'smtp-process-filter)
161 (if (eq smtp-connection-type 'force)
162 (starttls-negotiate process))
165 (setq response (smtp-read-response process))
166 (if (or (null (car response))
167 (not (integerp (car response)))
168 (>= (car response) 400))
169 (throw 'done (car (cdr response))))
172 (smtp-send-command process
173 (format "EHLO %s" (smtp-make-fqdn)))
174 (setq response (smtp-read-response process))
175 (if (or (null (car response))
176 (not (integerp (car response)))
177 (>= (car response) 400))
180 (smtp-send-command process
181 (format "HELO %s" (smtp-make-fqdn)))
182 (setq response (smtp-read-response process))
183 (if (or (null (car response))
184 (not (integerp (car response)))
185 (>= (car response) 400))
186 (throw 'done (car (cdr response)))))
187 (let ((extension-lines (cdr (cdr response)))
189 (while extension-lines
193 (downcase (substring (car extension-lines) 4))))
194 (while (string-match "\\([^ ]+\\)" extension (match-end 1))
195 (push (intern (match-string 1 extension)) extensions))
196 (push (intern extension) extensions))
197 (setq extension-lines (cdr extension-lines)))))
199 ;; STARTTLS --- begin a TLS negotiation (RFC 2595)
200 (when (and smtp-connection-type
201 (null (eq smtp-connection-type 'force))
202 (memq 'starttls extensions))
203 (smtp-send-command process "STARTTLS")
204 (setq response (smtp-read-response process))
205 (if (or (null (car response))
206 (not (integerp (car response)))
207 (>= (car response) 400))
208 (throw 'done (car (cdr response))))
209 (starttls-negotiate process)
210 ;; for sendmail warning XXX
211 (smtp-send-command process (format "HELO %s" (smtp-make-fqdn)))
212 (setq response (smtp-read-response process)))
214 ;; AUTH --- SMTP Service Extension for Authentication (RFC2554)
215 (when smtp-authenticate-type
216 (let ((auth smtp-authenticate-type) method)
218 (memq auth extensions)
219 (setq method (nth 1 (assq auth smtp-authenticate-method-alist))))
220 (funcall method process)
222 (format "AUTH mechanism %s not available" auth)))))
224 ;; ONEX --- One message transaction only (sendmail extension?)
225 ;;; (if (or (memq 'onex extensions)
226 ;;; (memq 'xone extensions))
228 ;;; (smtp-send-command process "ONEX")
229 ;;; (setq response (smtp-read-response process))
230 ;;; (if (or (null (car response))
231 ;;; (not (integerp (car response)))
232 ;;; (>= (car response) 400))
233 ;;; (throw 'done (car (cdr response))))))
235 ;; VERB --- Verbose (sendmail extension?)
236 ;;; (if (and smtp-debug-info
237 ;;; (or (memq 'verb extensions)
238 ;;; (memq 'xvrb extensions)))
240 ;;; (smtp-send-command process "VERB")
241 ;;; (setq response (smtp-read-response process))
242 ;;; (if (or (null (car response))
243 ;;; (not (integerp (car response)))
244 ;;; (>= (car response) 400))
245 ;;; (throw 'done (car (cdr response))))))
247 ;; XUSR --- Initial (user) submission (sendmail extension?)
248 ;;; (if (memq 'xusr extensions)
250 ;;; (smtp-send-command process "XUSR")
251 ;;; (setq response (smtp-read-response process))
252 ;;; (if (or (null (car response))
253 ;;; (not (integerp (car response)))
254 ;;; (>= (car response) 400))
255 ;;; (throw 'done (car (cdr response))))))
257 ;; MAIL FROM:<sender>
260 (format "MAIL FROM:<%s>%s%s"
262 ;; SIZE --- Message Size Declaration (RFC1870)
263 (if (memq 'size extensions)
266 (set-buffer smtp-text-buffer)
267 (+ (- (point-max) (point-min))
268 ;; Add one byte for each change-of-line
269 ;; because or CR-LF representation:
270 (count-lines (point-min) (point-max))
271 ;; For some reason, an empty line is
272 ;; added to the message. Maybe this
273 ;; is a bug, but it can't hurt to add
274 ;; those two bytes anyway:
277 ;; 8BITMIME --- 8bit-MIMEtransport (RFC1652)
278 (if (and (memq '8bitmime extensions)
282 (setq response (smtp-read-response process))
283 (if (or (null (car response))
284 (not (integerp (car response)))
285 (>= (car response) 400))
286 (throw 'done (car (cdr response))))
288 ;; RCPT TO:<recipient>
290 (smtp-send-command process
292 (if smtp-notify-success
293 "RCPT TO:<%s> NOTIFY=SUCCESS"
296 (setq recipients (cdr recipients))
297 (setq response (smtp-read-response process))
298 (if (or (null (car response))
299 (not (integerp (car response)))
300 (>= (car response) 400))
301 (throw 'done (car (cdr response)))))
304 (smtp-send-command process "DATA")
305 (setq response (smtp-read-response process))
306 (if (or (null (car response))
307 (not (integerp (car response)))
308 (>= (car response) 400))
309 (throw 'done (car (cdr response))))
312 (smtp-send-data process smtp-text-buffer)
315 (smtp-send-command process ".")
316 (setq response (smtp-read-response process))
317 (if (or (null (car response))
318 (not (integerp (car response)))
319 (>= (car response) 400))
320 (throw 'done (car (cdr response))))
325 (memq (process-status process) '(open run)))
328 (smtp-send-command process "QUIT")
329 (smtp-read-response process)
330 (delete-process process)))))))
332 (defun smtp-process-filter (process output)
334 (set-buffer (process-buffer process))
335 (goto-char (point-max))
338 (defun smtp-read-response (process)
339 (let ((case-fold-search nil)
340 (response-strings nil)
341 (response-continue t)
342 (return-value '(nil ()))
345 (while response-continue
346 (goto-char smtp-read-point)
347 (while (not (search-forward "\r\n" nil t))
348 (accept-process-output process)
349 (goto-char smtp-read-point))
351 (setq match-end (point))
352 (setq response-strings
353 (cons (buffer-substring smtp-read-point (- match-end 2))
356 (goto-char smtp-read-point)
357 (if (looking-at "[0-9]+ ")
358 (let ((begin (match-beginning 0))
361 (message "%s" (car response-strings)))
363 (setq smtp-read-point match-end)
365 ;; ignore lines that start with "0"
366 (if (looking-at "0[0-9]+ ")
368 (setq response-continue nil)
371 (buffer-substring begin end))
372 (nreverse response-strings)))))
374 (if (looking-at "[0-9]+-")
375 (progn (if smtp-debug-info
376 (message "%s" (car response-strings)))
377 (setq smtp-read-point match-end)
378 (setq response-continue t))
380 (setq smtp-read-point match-end)
381 (setq response-continue nil)
383 (cons nil (nreverse response-strings)))))))
384 (setq smtp-read-point match-end)
387 (defun smtp-send-command (process command &optional secure)
388 (goto-char (point-max))
390 (insert "Here is insecure words.\r\n")
391 (insert command "\r\n"))
392 (setq smtp-read-point (point))
393 (process-send-string process command)
394 (process-send-string process "\r\n"))
396 (defun smtp-send-data-1 (process data)
397 (goto-char (point-max))
399 (insert data "\r\n"))
400 (setq smtp-read-point (point))
401 ;; Escape "." at start of a line.
402 (if (eq (string-to-char data) ?.)
403 (process-send-string process "."))
404 (process-send-string process data)
405 (process-send-string process "\r\n"))
407 (defun smtp-send-data (process buffer)
408 (let ((data-continue t)
415 (goto-char (point-min)))
421 (setq this-line (point))
423 (setq this-line-end (point))
424 (setq sending-data nil)
425 (setq sending-data (buffer-substring this-line this-line-end))
426 (if (or (/= (forward-line 1) 0) (eobp))
427 (setq data-continue nil)))
429 (smtp-send-data-1 process sending-data))))
431 (defun smtp-deduce-address-list (smtp-text-buffer header-start header-end)
432 "Get address list suitable for smtp RCPT TO:<address>."
433 (let ((simple-address-list "")
437 (smtp-address-buffer (generate-new-buffer " *smtp-mail*")))
441 (set-buffer smtp-address-buffer)
442 (setq case-fold-search t)
444 (insert (save-excursion
445 (set-buffer smtp-text-buffer)
446 (buffer-substring-no-properties header-start header-end)))
447 (goto-char (point-min))
448 ;; RESENT-* fields should stop processing of regular fields.
450 (if (re-search-forward "^RESENT-TO:" header-end t)
452 "^\\(RESENT-TO:\\|RESENT-CC:\\|RESENT-BCC:\\)")
453 (setq addr-regexp "^\\(TO:\\|CC:\\|BCC:\\)")))
455 (while (re-search-forward addr-regexp header-end t)
457 (setq this-line (match-beginning 0))
459 ;; get any continuation lines.
460 (while (and (looking-at "^[ \t]+") (< (point) header-end))
462 (setq this-line-end (point-marker))
463 (setq simple-address-list
464 (concat simple-address-list " "
465 (mail-strip-quoted-names
466 (buffer-substring this-line this-line-end)))))
469 (insert-string simple-address-list)
472 (subst-char-in-region (point-min) (point-max) 10 ? t)
474 (subst-char-in-region (point-min) (point-max) ?, ? t)
476 (subst-char-in-region (point-min) (point-max) 9 ? t)
478 (goto-char (point-min))
479 ;; tidyness in case hook is not robust when it looks at this
480 (while (re-search-forward "[ \t]+" header-end t) (replace-match " "))
482 (goto-char (point-min))
483 (let (recipient-address-list)
484 (while (re-search-forward " \\([^ ]+\\) " (point-max) t)
486 (setq recipient-address-list
487 (cons (buffer-substring (match-beginning 1) (match-end 1))
488 recipient-address-list)))
489 recipient-address-list))
490 (kill-buffer smtp-address-buffer))))
492 (defun smtp-auth-cram-md5 (process)
493 (let ((secure-word (copy-sequence smtp-authenticate-passphrase))
495 (smtp-send-command process "AUTH CRAM-MD5")
496 (setq response (smtp-read-response process))
497 (if (or (null (car response))
498 (not (integerp (car response)))
499 (>= (car response) 400))
500 (throw 'done (car (cdr response))))
503 (setq secure-word (unwind-protect
505 smtp-authenticate-user secure-word
506 (base64-decode-string
507 (substring (car (cdr response)) 4)))
508 (fillarray secure-word 0))
509 secure-word (unwind-protect
510 (base64-encode-string secure-word)
511 (fillarray secure-word 0))) t)
512 (fillarray secure-word 0)
513 (setq response (smtp-read-response process))
514 (if (or (null (car response))
515 (not (integerp (car response)))
516 (>= (car response) 400))
517 (throw 'done (car (cdr response))))))
519 (defun smtp-auth-plain (process)
520 (let ((secure-word (copy-sequence smtp-authenticate-passphrase))
524 (setq secure-word (unwind-protect
525 (sasl-plain "" smtp-authenticate-user secure-word)
526 (fillarray secure-word 0))
527 secure-word (unwind-protect
528 (base64-encode-string secure-word)
529 (fillarray secure-word 0))
530 secure-word (unwind-protect
531 (concat "AUTH PLAIN " secure-word)
532 (fillarray secure-word 0))) t)
533 (fillarray secure-word 0)
534 (setq response (smtp-read-response process))
535 (if (or (null (car response))
536 (not (integerp (car response)))
537 (>= (car response) 400))
538 (throw 'done (car (cdr response))))))
540 (defun smtp-auth-login (process)
541 (let ((secure-word (copy-sequence smtp-authenticate-passphrase))
543 (smtp-send-command process "AUTH LOGIN")
544 (setq response (smtp-read-response process))
545 (if (or (null (car response))
546 (not (integerp (car response)))
547 (>= (car response) 400))
548 (throw 'done (car (cdr response))))
551 (base64-encode-string
552 smtp-authenticate-user))
553 (setq response (smtp-read-response process))
554 (if (or (null (car response))
555 (not (integerp (car response)))
556 (>= (car response) 400))
557 (throw 'done (car (cdr response))))
560 (setq secure-word (unwind-protect
561 (base64-encode-string secure-word)
562 (fillarray secure-word 0))) t)
563 (fillarray secure-word 0)
564 (setq response (smtp-read-response process))
565 (if (or (null (car response))
566 (not (integerp (car response)))
567 (>= (car response) 400))
568 (throw 'done (car (cdr response))))))
570 (defun smtp-auth-anonymous (process &optional token)
573 process "AUTH ANONYMOUS")
574 (setq response (smtp-read-response process))
575 (if (or (null (car response))
576 (not (integerp (car response)))
577 (>= (car response) 400))
578 (throw 'done (car (cdr response))))
579 (smtp-send-command process
580 (base64-encode-string
584 (setq response (smtp-read-response process))
585 (if (or (null (car response))
586 (not (integerp (car response)))
587 (>= (car response) 400))
588 (throw 'done (car (cdr response))))))
590 (defun smtp-auth-scram-md5 (process)
592 (let (server-msg-1 server-msg-2 client-msg-1 salted-pass
593 response secure-word)
594 (smtp-send-command process "AUTH SCRAM-MD5")
595 (setq response (smtp-read-response process))
596 (if (or (null (car response))
597 (not (integerp (car response)))
598 (>= (car response) 400))
599 (throw 'done (car (cdr response))))
604 (base64-encode-string
606 (sasl-scram-md5-client-msg-1
607 smtp-authenticate-user)))) t)
608 (fillarray secure-word 0))
609 (setq response (smtp-read-response process))
610 (if (or (null (car response))
611 (not (integerp (car response)))
612 (>= (car response) 400))
614 (fillarray client-msg-1 0)
615 (throw 'done (car (cdr response)))))
618 (substring (car (cdr response)) 4)
619 (fillarray (car (cdr response)) 0)))
622 (base64-decode-string secure-word)
623 (fillarray secure-word 0)))
625 (sasl-scram-md5-client-msg-2
626 server-msg-1 client-msg-1
628 (sasl-scram-md5-make-salted-pass
629 smtp-authenticate-passphrase server-msg-1))))
632 (base64-encode-string secure-word)
633 (fillarray secure-word 0)))
635 (smtp-send-command process secure-word t)
636 (fillarray secure-word 0))
637 (setq response (smtp-read-response process))
638 (if (or (null (car response))
639 (not (integerp (car response)))
640 (>= (car response) 400))
642 (fillarray salted-pass 0)
643 (fillarray server-msg-1 0)
644 (fillarray client-msg-1 0)
645 (throw 'done (car (cdr response)))))
648 (base64-decode-string
650 (substring (car (cdr response)) 4)))
651 (fillarray secure-word 0)))
654 (sasl-scram-md5-authenticate-server
659 (fillarray salted-pass 0)
660 (fillarray server-msg-1 0)
661 (fillarray server-msg-2 0)
662 (fillarray client-msg-1 0)))
664 (smtp-send-command process "")
665 (setq response (smtp-read-response process))
666 (if (or (null (car response))
667 (not (integerp (car response)))
668 (>= (car response) 400))
669 (throw 'done (car (cdr response)))) ))
671 (defun smtp-auth-digest-md5 (process)
672 "Login to server using the AUTH DIGEST-MD5 method."
673 (let (user realm response)
674 (smtp-send-command process "AUTH DIGEST-MD5")
675 (setq response (smtp-read-response process))
676 (if (or (null (car response))
677 (not (integerp (car response)))
678 (>= (car response) 400))
679 (throw 'done (car (cdr response))))
680 (if (string-match "^\\([^@]*\\)@\\([^@]*\\)"
681 smtp-authenticate-user)
682 (setq user (match-string 1 smtp-authenticate-user)
683 realm (match-string 2 smtp-authenticate-user))
684 (setq user smtp-authenticate-user
686 (smtp-send-command process
687 (base64-encode-string
688 (sasl-digest-md5-digest-response
689 (base64-decode-string
690 (substring (car (cdr response)) 4))
692 smtp-authenticate-passphrase
693 "smtp" smtp-server realm)
695 (setq response (smtp-read-response process))
696 (if (or (null (car response))
697 (not (integerp (car response)))
698 (>= (car response) 400))
699 (throw 'done (car (cdr response))))
700 (smtp-send-command process "")))
704 ;;; smtp.el ends here