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
38 (autoload 'starttls-open-stream "starttls")
39 (autoload 'starttls-negotiate "starttls")
40 (autoload 'sasl-cram-md5 "sasl")
41 (autoload 'sasl-plain "sasl"))
43 (eval-when-compile (require 'cl)) ; push
46 "SMTP protocol for sending mail."
49 (defcustom smtp-default-server nil
50 "*Specify default SMTP server."
51 :type '(choice (const nil) string)
54 (defcustom smtp-server (or (getenv "SMTPSERVER") smtp-default-server)
55 "*The name of the host running SMTP server. 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-service "smtp"
62 "*SMTP service port number. \"smtp\" or 25."
63 :type '(choice (integer :tag "25" 25)
64 (string :tag "smtp" "smtp"))
67 (defcustom smtp-use-8bitmime t
68 "*If non-nil, use ESMTP 8BITMIME if available."
72 (defcustom smtp-local-domain nil
73 "*Local domain name without a host name.
74 If the function (system-name) returns the full internet address,
75 don't define this value."
76 :type '(choice (const nil) string)
79 (defcustom smtp-debug-info nil
80 "*smtp debug info printout. messages and process buffer."
84 (defcustom smtp-notify-success nil
85 "*If non-nil, notification for successful mail delivery is returned
90 (defcustom smtp-authentication-type nil
91 "*SMTP authentication mechanism (RFC2554)."
95 (defvar smtp-authentication-user nil)
96 (defvar smtp-authentication-passphrase nil)
98 (defvar smtp-authentication-method-alist
99 '((cram-md5 smtp-auth-cram-md5)
100 (plain smtp-auth-plain)
101 (login smtp-auth-login)
102 (anonymous smtp-auth-anonymous)
103 (scram-md5 smtp-auth-scram-md5)
106 (defcustom smtp-connection-type nil
107 "*SMTP connection type."
108 :type '(choice (const nil) (const :tag "TLS" starttls))
111 (defvar smtp-read-point nil)
113 (defun smtp-make-fqdn ()
114 "Return user's fully qualified domain name."
115 (let ((system-name (system-name)))
118 (concat system-name "." smtp-local-domain))
119 ((string-match "[^.]\\.[^.]" system-name)
122 (error "Cannot generate valid FQDN. Set `smtp-local-domain' correctly.")))))
124 (defun smtp-via-smtp (sender recipients smtp-text-buffer)
125 (let ((server (if (functionp smtp-server)
126 (funcall smtp-server sender recipients)
128 process response extensions)
132 (format "*trace of SMTP session to %s*" server)))
134 (make-local-variable 'smtp-read-point)
135 (setq smtp-read-point (point-min))
140 (if smtp-connection-type
142 (starttls-open-stream
143 "SMTP" (current-buffer) server smtp-service))
144 (open-network-stream-as-binary
145 "SMTP" (current-buffer) server smtp-service)))
147 (set-process-filter process 'smtp-process-filter)
149 (if (eq smtp-connection-type 'force)
150 (starttls-negotiate process))
153 (setq response (smtp-read-response process))
154 (if (or (null (car response))
155 (not (integerp (car response)))
156 (>= (car response) 400))
157 (throw 'done (car (cdr response))))
160 (smtp-send-command process
161 (format "EHLO %s" (smtp-make-fqdn)))
162 (setq response (smtp-read-response process))
163 (if (or (null (car response))
164 (not (integerp (car response)))
165 (>= (car response) 400))
168 (smtp-send-command process
169 (format "HELO %s" (smtp-make-fqdn)))
170 (setq response (smtp-read-response process))
171 (if (or (null (car response))
172 (not (integerp (car response)))
173 (>= (car response) 400))
174 (throw 'done (car (cdr response)))))
175 (let ((extension-lines (cdr (cdr response)))
177 (while extension-lines
181 (downcase (substring (car extension-lines) 4))))
182 (while (string-match "\\([^ ]+\\)" extension (match-end 1))
183 (push (intern (match-string 1 extension)) extensions))
184 (push (intern extension) extensions))
185 (setq extension-lines (cdr extension-lines)))))
187 ;; STARTTLS --- begin a TLS negotiation (RFC 2595)
188 (when (and smtp-connection-type
189 (null (eq smtp-connection-type 'force))
190 (memq 'starttls extensions))
191 (smtp-send-command process "STARTTLS")
192 (setq response (smtp-read-response process))
193 (if (or (null (car response))
194 (not (integerp (car response)))
195 (>= (car response) 400))
196 (throw 'done (car (cdr response))))
197 (starttls-negotiate process))
199 ;; AUTH --- SMTP Service Extension for Authentication (RFC2554)
200 (when smtp-authentication-type
201 (let ((auth (intern smtp-authentication-type)) method)
203 (memq auth extensions)
204 (setq method (nth 1 (assq auth smtp-authentication-method-alist))))
205 (funcall method process)
207 (format "AUTH mechanism %s not available" auth)))))
209 ;; ONEX --- One message transaction only (sendmail extension?)
210 ;;; (if (or (memq 'onex extensions)
211 ;;; (memq 'xone extensions))
213 ;;; (smtp-send-command process "ONEX")
214 ;;; (setq response (smtp-read-response process))
215 ;;; (if (or (null (car response))
216 ;;; (not (integerp (car response)))
217 ;;; (>= (car response) 400))
218 ;;; (throw 'done (car (cdr response))))))
220 ;; VERB --- Verbose (sendmail extension?)
221 ;;; (if (and smtp-debug-info
222 ;;; (or (memq 'verb extensions)
223 ;;; (memq 'xvrb extensions)))
225 ;;; (smtp-send-command process "VERB")
226 ;;; (setq response (smtp-read-response process))
227 ;;; (if (or (null (car response))
228 ;;; (not (integerp (car response)))
229 ;;; (>= (car response) 400))
230 ;;; (throw 'done (car (cdr response))))))
232 ;; XUSR --- Initial (user) submission (sendmail extension?)
233 ;;; (if (memq 'xusr extensions)
235 ;;; (smtp-send-command process "XUSR")
236 ;;; (setq response (smtp-read-response process))
237 ;;; (if (or (null (car response))
238 ;;; (not (integerp (car response)))
239 ;;; (>= (car response) 400))
240 ;;; (throw 'done (car (cdr response))))))
242 ;; MAIL FROM:<sender>
245 (format "MAIL FROM:<%s>%s%s"
247 ;; SIZE --- Message Size Declaration (RFC1870)
248 (if (memq 'size extensions)
251 (set-buffer smtp-text-buffer)
252 (+ (- (point-max) (point-min))
253 ;; Add one byte for each change-of-line
254 ;; because or CR-LF representation:
255 (count-lines (point-min) (point-max))
256 ;; For some reason, an empty line is
257 ;; added to the message. Maybe this
258 ;; is a bug, but it can't hurt to add
259 ;; those two bytes anyway:
262 ;; 8BITMIME --- 8bit-MIMEtransport (RFC1652)
263 (if (and (memq '8bitmime extensions)
267 (setq response (smtp-read-response process))
268 (if (or (null (car response))
269 (not (integerp (car response)))
270 (>= (car response) 400))
271 (throw 'done (car (cdr response))))
273 ;; RCPT TO:<recipient>
275 (smtp-send-command process
277 (if smtp-notify-success
278 "RCPT TO:<%s> NOTIFY=SUCCESS"
281 (setq recipients (cdr recipients))
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)))))
289 (smtp-send-command process "DATA")
290 (setq response (smtp-read-response process))
291 (if (or (null (car response))
292 (not (integerp (car response)))
293 (>= (car response) 400))
294 (throw 'done (car (cdr response))))
297 (smtp-send-data process smtp-text-buffer)
300 (smtp-send-command process ".")
301 (setq response (smtp-read-response process))
302 (if (or (null (car response))
303 (not (integerp (car response)))
304 (>= (car response) 400))
305 (throw 'done (car (cdr response))))
310 (eq (process-status process) 'open))
313 (smtp-send-command process "QUIT")
314 (smtp-read-response process)
315 (delete-process process)))))))
317 (defun smtp-process-filter (process output)
319 (set-buffer (process-buffer process))
320 (goto-char (point-max))
323 (defun smtp-read-response (process)
324 (let ((case-fold-search nil)
325 (response-strings nil)
326 (response-continue t)
327 (return-value '(nil ()))
330 (while response-continue
331 (goto-char smtp-read-point)
332 (while (not (search-forward "\r\n" nil t))
333 (accept-process-output process)
334 (goto-char smtp-read-point))
336 (setq match-end (point))
337 (setq response-strings
338 (cons (buffer-substring smtp-read-point (- match-end 2))
341 (goto-char smtp-read-point)
342 (if (looking-at "[0-9]+ ")
343 (let ((begin (match-beginning 0))
346 (message "%s" (car response-strings)))
348 (setq smtp-read-point match-end)
350 ;; ignore lines that start with "0"
351 (if (looking-at "0[0-9]+ ")
353 (setq response-continue nil)
356 (buffer-substring begin end))
357 (nreverse response-strings)))))
359 (if (looking-at "[0-9]+-")
360 (progn (if smtp-debug-info
361 (message "%s" (car response-strings)))
362 (setq smtp-read-point match-end)
363 (setq response-continue t))
365 (setq smtp-read-point match-end)
366 (setq response-continue nil)
368 (cons nil (nreverse response-strings)))))))
369 (setq smtp-read-point match-end)
372 (defun smtp-send-command (process command &optional secure)
373 (goto-char (point-max))
375 (insert "Here is insecure words.\r\n")
376 (insert command "\r\n"))
377 (setq smtp-read-point (point))
378 (process-send-string process command)
379 (process-send-string process "\r\n"))
381 (defun smtp-send-data-1 (process data)
382 (goto-char (point-max))
384 (insert data "\r\n"))
385 (setq smtp-read-point (point))
386 ;; Escape "." at start of a line.
387 (if (eq (string-to-char data) ?.)
388 (process-send-string process "."))
389 (process-send-string process data)
390 (process-send-string process "\r\n"))
392 (defun smtp-send-data (process buffer)
393 (let ((data-continue t)
400 (goto-char (point-min)))
406 (setq this-line (point))
408 (setq this-line-end (point))
409 (setq sending-data nil)
410 (setq sending-data (buffer-substring this-line this-line-end))
411 (if (or (/= (forward-line 1) 0) (eobp))
412 (setq data-continue nil)))
414 (smtp-send-data-1 process sending-data))))
416 (defun smtp-deduce-address-list (smtp-text-buffer header-start header-end)
417 "Get address list suitable for smtp RCPT TO:<address>."
418 (let ((case-fold-search t)
419 (simple-address-list "")
423 (smtp-address-buffer (generate-new-buffer " *smtp-mail*")))
427 (set-buffer smtp-address-buffer)
429 (insert (save-excursion
430 (set-buffer smtp-text-buffer)
431 (buffer-substring-no-properties header-start header-end)))
432 (goto-char (point-min))
433 ;; RESENT-* fields should stop processing of regular fields.
435 (if (re-search-forward "^RESENT-TO:" header-end t)
437 "^\\(RESENT-TO:\\|RESENT-CC:\\|RESENT-BCC:\\)")
438 (setq addr-regexp "^\\(TO:\\|CC:\\|BCC:\\)")))
440 (while (re-search-forward addr-regexp header-end t)
442 (setq this-line (match-beginning 0))
444 ;; get any continuation lines.
445 (while (and (looking-at "^[ \t]+") (< (point) header-end))
447 (setq this-line-end (point-marker))
448 (setq simple-address-list
449 (concat simple-address-list " "
450 (mail-strip-quoted-names
451 (buffer-substring this-line this-line-end)))))
454 (insert-string simple-address-list)
457 (subst-char-in-region (point-min) (point-max) 10 ? t)
459 (subst-char-in-region (point-min) (point-max) ?, ? t)
461 (subst-char-in-region (point-min) (point-max) 9 ? t)
463 (goto-char (point-min))
464 ;; tidyness in case hook is not robust when it looks at this
465 (while (re-search-forward "[ \t]+" header-end t) (replace-match " "))
467 (goto-char (point-min))
468 (let (recipient-address-list)
469 (while (re-search-forward " \\([^ ]+\\) " (point-max) t)
471 (setq recipient-address-list
472 (cons (buffer-substring (match-beginning 1) (match-end 1))
473 recipient-address-list)))
474 recipient-address-list))
475 (kill-buffer smtp-address-buffer))))
477 (defun smtp-auth-cram-md5 (process)
478 (let ((secure-word (copy-sequence smtp-authentication-passphrase))
480 (smtp-send-command process "AUTH CRAM-MD5")
481 (setq response (smtp-read-response process))
482 (if (or (null (car response))
483 (not (integerp (car response)))
484 (>= (car response) 400))
485 (throw 'done (car (cdr response))))
488 (setq secure-word (unwind-protect
490 smtp-authentication-user secure-word
491 (base64-decode-string
492 (substring (car (cdr response)) 4)))
493 (fillarray secure-word 0))
494 secure-word (unwind-protect
495 (base64-encode-string secure-word)
496 (fillarray secure-word 0))) t)
497 (fillarray secure-word 0)
498 (setq response (smtp-read-response process))
499 (if (or (null (car response))
500 (not (integerp (car response)))
501 (>= (car response) 400))
502 (throw 'done (car (cdr response))))))
504 (defun smtp-auth-plain (process)
505 (let ((secure-word (copy-sequence smtp-authentication-passphrase))
509 (setq secure-word (unwind-protect
510 (sasl-plain "" smtp-authentication-user secure-word)
511 (fillarray secure-word 0))
512 secure-word (unwind-protect
513 (base64-encode-string secure-word)
514 (fillarray secure-word 0))
515 secure-word (unwind-protect
516 (concat "AUTH PLAIN " secure-word)
517 (fillarray secure-word 0))) t)
518 (fillarray secure-word 0)
519 (setq response (smtp-read-response process))
520 (if (or (null (car response))
521 (not (integerp (car response)))
522 (>= (car response) 400))
523 (throw 'done (car (cdr response))))))
525 (defun smtp-auth-login (process)
526 (let ((secure-word (copy-sequence smtp-authentication-passphrase))
530 (concat "AUTH LOGIN " smtp-authentication-user))
531 (setq response (smtp-read-response process))
532 (if (or (null (car response))
533 (not (integerp (car response)))
534 (>= (car response) 400))
535 (throw 'done (car (cdr response))))
538 (setq secure-word (unwind-protect
539 (base64-encode-string secure-word)
540 (fillarray secure-word 0))) t)
541 (fillarray secure-word 0)
542 (setq response (smtp-read-response process))
543 (if (or (null (car response))
544 (not (integerp (car response)))
545 (>= (car response) 400))
546 (throw 'done (car (cdr response))))))
548 (defun smtp-auth-anonymous (process &optional token)
551 process "AUTH ANONYMOUS")
552 (setq response (smtp-read-response process))
553 (if (or (null (car response))
554 (not (integerp (car response)))
555 (>= (car response) 400))
556 (throw 'done (car (cdr response))))
557 (smtp-send-command process
558 (base64-encode-string
562 (setq response (smtp-read-response process))
563 (if (or (null (car response))
564 (not (integerp (car response)))
565 (>= (car response) 400))
566 (throw 'done (car (cdr response))))))
568 (defun smtp-auth-scram-md5 (process)
570 (let (server-msg-1 server-msg-2 client-msg-1 salted-pass
571 response secure-word)
572 (smtp-send-command process "AUTH SCRAM-MD5")
573 (setq response (smtp-read-response process))
574 (if (or (null (car response))
575 (not (integerp (car response)))
576 (>= (car response) 400))
577 (throw 'done (car (cdr response))))
582 (base64-encode-string
584 (sasl-scram-md5-client-msg-1
585 smtp-authentication-user)))) t)
586 (fillarray secure-word 0))
587 (setq response (smtp-read-response process))
588 (if (or (null (car response))
589 (not (integerp (car response)))
590 (>= (car response) 400))
592 (fillarray client-msg-1 0)
593 (throw 'done (car (cdr response)))))
596 (substring (car (cdr response)) 4)
597 (fillarray (car (cdr response)) 0)))
600 (base64-decode-string secure-word)
601 (fillarray secure-word 0)))
603 (sasl-scram-md5-client-msg-2
604 server-msg-1 client-msg-1
606 (scram-md5-make-salted-pass
607 smtp-authentication-passphrase
609 (scram-md5-parse-server-msg-1 server-msg-1))))))
612 (base64-encode-string secure-word)
613 (fillarray secure-word 0)))
615 (smtp-send-command process secure-word t)
616 (fillarray secure-word 0))
617 (setq response (smtp-read-response process))
618 (if (or (null (car response))
619 (not (integerp (car response)))
620 (>= (car response) 400))
622 (fillarray salted-pass 0)
623 (fillarray server-msg-1 0)
624 (fillarray client-msg-1 0)
625 (throw 'done (car (cdr response)))))
628 (base64-decode-string
630 (substring (car (cdr response)) 4)))
631 (fillarray secure-word 0)))
634 (sasl-scram-md5-authenticate-server
639 (fillarray salted-pass 0)
640 (fillarray server-msg-1 0)
641 (fillarray server-msg-2 0)
642 (fillarray client-msg-1 0)))
644 (smtp-send-command process "")
645 (setq response (smtp-read-response process))
646 (if (or (null (car response))
647 (not (integerp (car response)))
648 (>= (car response) 400))
649 (throw 'done (car (cdr response)))) ))
653 ;;; smtp.el ends here