Fix my email address.
[elisp/flim.git] / smtp.el
1 ;;; smtp.el --- basic functions to send mail with SMTP server
2
3 ;; Copyright (C) 1995, 1996, 1998, 1999, 2000 Free Software Foundation, Inc.
4
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
10
11 ;; This file is part of FLIM (Faithful Library about Internet Message).
12
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.
17
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.
22
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.
27
28
29 ;;; Commentary:
30 ;;
31
32 ;;; Code:
33
34 (require 'pces)
35 (require 'pcustom)
36 (require 'mail-utils)                   ; mail-strip-quoted-names
37 (require 'sasl)
38 (require 'luna)
39 (require 'mel) ; binary-funcall
40
41 (defgroup smtp nil
42   "SMTP protocol for sending mail."
43   :group 'mail)
44
45 (defgroup smtp-extensions nil
46   "SMTP service extensions (RFC1869)."
47   :group 'smtp)
48
49 (defcustom smtp-default-server nil
50   "Specify default SMTP server."
51   :type '(choice (const nil) string)
52   :group 'smtp)
53
54 (defcustom smtp-server (or (getenv "SMTPSERVER") smtp-default-server)
55   "The name of the host running SMTP server.
56 It can also be a function
57 called from `smtp-via-smtp' with arguments SENDER and RECIPIENTS."
58   :type '(choice (string :tag "Name")
59                  (function :tag "Function"))
60   :group 'smtp)
61
62 (defcustom smtp-service "smtp"
63   "SMTP service port number.  \"smtp\" or 25."
64   :type '(choice (integer :tag "25" 25)
65                  (string :tag "smtp" "smtp"))
66   :group 'smtp)
67
68 (defcustom smtp-local-domain nil
69   "Local domain name without a host name.
70 If the function (system-name) returns the full internet address,
71 don't define this value."
72   :type '(choice (const nil) string)
73   :group 'smtp)
74
75 (defcustom smtp-fqdn nil
76   "Fully qualified domain name used for Message-ID."
77   :type '(choice (const nil) string)
78   :group 'smtp)
79
80 (defcustom smtp-use-8bitmime t
81   "If non-nil, use ESMTP 8BITMIME (RFC1652) if available."
82   :type 'boolean
83   :group 'smtp-extensions)
84
85 (defcustom smtp-use-size t
86   "If non-nil, use ESMTP SIZE (RFC1870) if available."
87   :type 'boolean
88   :group 'smtp-extensions)
89
90 (defcustom smtp-use-starttls nil
91   "If non-nil, use STARTTLS (RFC2595) if available."
92   :type 'boolean
93   :group 'smtp-extensions)
94
95 (defcustom smtp-use-sasl nil
96   "If non-nil, use SMTP Authentication (RFC2554) if available."
97   :type 'boolean
98   :group 'smtp-extensions)
99
100 (defcustom smtp-sasl-user-name (user-login-name)
101   "Identification to be used for authorization."
102   :type 'string
103   :group 'smtp-extensions)
104
105 (defcustom smtp-sasl-properties nil
106   "Properties set to SASL client."
107   :type 'string
108   :group 'smtp-extensions)
109
110 (defcustom smtp-sasl-mechanisms nil
111   "List of authentication mechanisms."
112   :type '(repeat string)
113   :group 'smtp-extensions)
114
115 (defvar sasl-mechanisms)
116 ;;;###autoload
117 (defvar smtp-open-connection-function (function open-network-stream)
118   "*Function used for connecting to a SMTP server.
119 The function will be called with the same four arguments as
120 `open-network-stream' and should return a process object.
121 Here is an example:
122
123 \(setq smtp-open-connection-function
124       (function
125        (lambda (name buffer host service)
126          (let ((process-connection-type nil))
127            (start-process name buffer \"ssh\" \"-C\" host
128                           \"nc\" host service)))))
129
130 It connects to a SMTP server using \"ssh\" before actually connecting
131 to the SMTP port.  Where the command \"nc\" is the netcat executable;
132 see http://www.atstake.com/research/tools/index.html#network_utilities
133 for details.  In addition, you will have to modify the value for
134 `smtp-end-of-line' to \"\\n\" if you use \"telnet\" instead of \"nc\".")
135
136 (defvar smtp-read-point nil)
137
138 (defvar smtp-connection-alist nil)
139
140 (defvar smtp-submit-package-function (function smtp-submit-package))
141
142 (defvar smtp-end-of-line "\r\n"
143   "*String to use as end-of-line marker when talking to a SMTP server.
144 This is \"\\r\\n\" by default, but it may have to be \"\\n\" when using a non
145 native connection function.  See also `smtp-open-connection-function'.")
146
147 ;;; @ SMTP package
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).
152
153 (eval-and-compile
154   (luna-define-class smtp-package ()
155                      (sender
156                       recipients
157                       buffer))
158
159   (luna-define-internal-accessors 'smtp-package))
160
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))
168
169 (defun smtp-package-buffer-internal-size (package)
170   "Return the size of PACKAGE, an integer."
171   (save-excursion
172     (set-buffer (smtp-package-buffer-internal package))
173     (let ((size
174            (+ (buffer-size)
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:
182               2)))
183       (goto-char (point-min))
184       (while (re-search-forward "^\\." nil t)
185         (setq size (1+ size)))
186       size)))
187
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.
193
194 (eval-and-compile
195   (luna-define-class smtp-connection ()
196                      (process
197                       server
198                       service
199                       extensions
200                       encoder
201                       decoder))
202
203   (luna-define-internal-accessors 'smtp-connection))
204
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))
210
211 (luna-define-generic smtp-connection-opened (connection)
212   "Say whether the CONNECTION to server has been opened.")
213
214 (luna-define-generic smtp-close-connection (connection)
215   "Close the CONNECTION to server.")
216
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))
220         t)))
221
222 (luna-define-method smtp-close-connection ((connection smtp-connection))
223   (let ((process (smtp-connection-process-internal connection)))
224     (delete-process process)))
225
226 (defun smtp-make-fqdn ()
227   "Return user's fully qualified domain name."
228   (if smtp-fqdn
229       smtp-fqdn
230     (let ((system-name (system-name)))
231       (cond
232        (smtp-local-domain
233         (concat system-name "." smtp-local-domain))
234        ((string-match "[^.]\\.[^.]" system-name)
235         system-name)
236        (t
237         (error "Cannot generate valid FQDN"))))))
238
239 (defun smtp-find-connection (buffer)
240   "Find the connection delivering to BUFFER."
241   (let ((entry (assq buffer smtp-connection-alist))
242         connection)
243     (when entry
244       (setq connection (nth 1 entry))
245       (if (smtp-connection-opened connection)
246           connection
247         (setq smtp-connection-alist
248               (delq entry smtp-connection-alist))
249         nil))))
250
251 (eval-and-compile
252   (autoload 'starttls-open-stream "starttls")
253   (autoload 'starttls-negotiate "starttls"))
254
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."
260   (let* ((process
261           (binary-funcall smtp-open-connection-function
262                           "SMTP" buffer server service))
263          connection)
264     (when process
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))
270       connection)))
271
272 ;;;###autoload
273 (defun smtp-via-smtp (sender recipients buffer)
274   "Like `smtp-send-buffer', but sucks in any errors."
275   (condition-case nil
276       (progn
277         (smtp-send-buffer sender recipients buffer)
278         t)
279     (smtp-error)))
280
281 (make-obsolete 'smtp-via-smtp "It's old API.")
282
283 ;;;###autoload
284 (defun smtp-send-buffer (sender recipients buffer)
285   "Send a message.
286 SENDER is an envelope sender address.
287 RECIPIENTS is a list of envelope recipient addresses.
288 BUFFER may be a buffer or a buffer name which contains mail message."
289   (let ((server
290          (if (functionp smtp-server)
291              (funcall smtp-server sender recipients)
292            smtp-server))
293         (package
294          (smtp-make-package sender recipients buffer))
295         (smtp-open-connection-function
296          (if smtp-use-starttls
297              (function starttls-open-stream)
298            smtp-open-connection-function)))
299     (save-excursion
300       (set-buffer
301        (get-buffer-create
302         (format "*trace of SMTP session to %s*" server)))
303       (erase-buffer)
304       (buffer-disable-undo (current-buffer))
305       (unless (smtp-find-connection (current-buffer))
306         (smtp-open-connection (current-buffer) server smtp-service))
307       (make-local-variable 'smtp-read-point)
308       (setq smtp-read-point (point-min))
309       (funcall smtp-submit-package-function package))))
310
311 (defun smtp-submit-package (package)
312   (unwind-protect
313       (progn
314         (smtp-primitive-greeting package)
315         (condition-case nil
316             (smtp-primitive-ehlo package)
317           (smtp-response-error
318            (smtp-primitive-helo package)))
319         (if smtp-use-starttls
320             (progn
321               (smtp-primitive-starttls package)
322               (smtp-primitive-ehlo package)))
323         (if smtp-use-sasl
324             (smtp-primitive-auth package))
325         (smtp-primitive-mailfrom package)
326         (smtp-primitive-rcptto package)
327         (smtp-primitive-data package))
328     (let ((connection (smtp-find-connection (current-buffer))))
329       (when (smtp-connection-opened connection)
330         (smtp-primitive-quit package)
331         (smtp-close-connection connection)))))
332
333 ;;; @ hook methods for `smtp-submit-package'
334 ;;;
335
336 (defun smtp-primitive-greeting (package)
337   (let* ((connection
338           (smtp-find-connection (current-buffer)))
339          (response
340           (smtp-read-response connection)))
341     (if (/= (car response) 220)
342         (smtp-response-error response))))
343
344 (defun smtp-primitive-ehlo (package)
345   (let* ((connection
346           (smtp-find-connection (current-buffer)))
347          response)
348     (smtp-send-command connection (format "EHLO %s" (smtp-make-fqdn)))
349     (setq response (smtp-read-response connection))
350     (if (/= (car response) 250)
351         (smtp-response-error response))
352     (smtp-connection-set-extensions-internal
353      connection (mapcar
354                  (lambda (extension)
355                    (let ((extensions
356                           (split-string extension)))
357                      (setcar extensions
358                              (car (read-from-string
359                                    (downcase (car extensions)))))
360                      extensions))
361                  (cdr response)))))
362
363 (defun smtp-primitive-helo (package)
364   (let* ((connection
365           (smtp-find-connection (current-buffer)))
366          response)
367     (smtp-send-command connection (format "HELO %s" (smtp-make-fqdn)))
368     (setq response (smtp-read-response connection))
369     (if (/= (car response) 250)
370         (smtp-response-error response))))
371
372 (defun smtp-primitive-auth (package)
373   (let* ((connection
374           (smtp-find-connection (current-buffer)))
375          (mechanisms
376           (cdr (assq 'auth (smtp-connection-extensions-internal connection))))
377          (sasl-mechanisms
378           (or smtp-sasl-mechanisms sasl-mechanisms))
379          (mechanism
380           (sasl-find-mechanism mechanisms))
381          client
382          name
383          step
384          response)
385     (unless mechanism
386       (error "No authentication mechanism available"))
387     (setq client (sasl-make-client mechanism smtp-sasl-user-name "smtp"
388                                    (smtp-connection-server-internal connection)))
389     (if smtp-sasl-properties
390         (sasl-client-set-properties client smtp-sasl-properties))
391     (setq name (sasl-mechanism-name mechanism)
392           ;; Retrieve the initial response
393           step (sasl-next-step client nil))
394     (smtp-send-command
395      connection
396      (if (sasl-step-data step)
397          (format "AUTH %s %s" name (base64-encode-string (sasl-step-data step) t))
398        (format "AUTH %s" name)))
399     (catch 'done
400       (while t
401         (setq response (smtp-read-response connection))
402         (when (= (car response) 235)
403           ;; The authentication process is finished.
404           (setq step (sasl-next-step client step))
405           (if (null step)
406               (throw 'done nil))
407           (smtp-response-error response)) ;Bogus server?
408         (if (/= (car response) 334)
409             (smtp-response-error response))
410         (sasl-step-set-data step (base64-decode-string (nth 1 response)))
411         (setq step (sasl-next-step client step))
412         (smtp-send-command
413          connection
414          (if (sasl-step-data step)
415              (base64-encode-string (sasl-step-data step) t)
416            ""))))
417     ;;;    (smtp-connection-set-encoder-internal
418     ;;;     connection (sasl-client-encoder client))
419     ;;;    (smtp-connection-set-decoder-internal
420     ;;;     connection (sasl-client-decoder client))
421     ))
422
423 (defun smtp-primitive-starttls (package)
424   (let* ((connection
425           (smtp-find-connection (current-buffer)))
426          response)
427     ;; STARTTLS --- begin a TLS negotiation (RFC 2595)
428     (smtp-send-command connection "STARTTLS")
429     (setq response (smtp-read-response connection))
430     (if (/= (car response) 220)
431         (smtp-response-error response))
432     (starttls-negotiate (smtp-connection-process-internal connection))))
433
434 (defun smtp-primitive-mailfrom (package)
435   (let* ((connection
436           (smtp-find-connection (current-buffer)))
437          (extensions
438           (smtp-connection-extensions-internal
439            connection))
440          (sender
441           (smtp-package-sender-internal package))
442          extension
443          response)
444     ;; SIZE --- Message Size Declaration (RFC1870)
445     (if (and smtp-use-size
446              (assq 'size extensions))
447         (setq extension (format "SIZE=%d" (smtp-package-buffer-internal-size package))))
448     ;; 8BITMIME --- 8bit-MIMEtransport (RFC1652)
449     (if (and smtp-use-8bitmime
450              (assq '8bitmime extensions))
451         (setq extension (concat extension " BODY=8BITMIME")))
452     (smtp-send-command
453      connection
454      (if extension
455          (format "MAIL FROM:<%s> %s" sender extension)
456        (format "MAIL FROM:<%s>" sender)))
457     (setq response (smtp-read-response connection))
458     (if (/= (car response) 250)
459         (smtp-response-error response))))
460
461 (defun smtp-primitive-rcptto (package)
462   (let* ((connection
463           (smtp-find-connection (current-buffer)))
464          (recipients
465           (smtp-package-recipients-internal package))
466          response)
467     (while recipients
468       (smtp-send-command
469        connection (format "RCPT TO:<%s>" (pop recipients)))
470       (setq response (smtp-read-response connection))
471       (unless (memq (car response) '(250 251))
472         (smtp-response-error response)))))
473
474 (defun smtp-primitive-data (package)
475   (let* ((connection
476           (smtp-find-connection (current-buffer)))
477          response)
478     (smtp-send-command connection "DATA")
479     (setq response (smtp-read-response connection))
480     (if (/= (car response) 354)
481         (smtp-response-error response))
482     (save-excursion
483       (set-buffer (smtp-package-buffer-internal package))
484       (goto-char (point-min))
485       (while (not (eobp))
486         (smtp-send-data
487          connection (buffer-substring (point) (progn (end-of-line)(point))))
488         (beginning-of-line 2)))
489     (smtp-send-command connection ".")
490     (setq response (smtp-read-response connection))
491     (if (/= (car response) 250)
492         (smtp-response-error response))))
493
494 (defun smtp-primitive-quit (package)
495   (let* ((connection
496           (smtp-find-connection (current-buffer)))
497          response)
498     (smtp-send-command connection "QUIT")
499     (setq response (smtp-read-response connection))
500     (if (/= (car response) 221)
501         (smtp-response-error response))))
502
503 ;;; @ low level process manipulating function
504 ;;;
505 (defun smtp-process-filter (process output)
506   (save-excursion
507     (set-buffer (process-buffer process))
508     (goto-char (point-max))
509     (insert output)))
510
511 (put 'smtp-error 'error-message "SMTP error")
512 (put 'smtp-error 'error-conditions '(smtp-error error))
513
514 (put 'smtp-response-error 'error-message "SMTP response error")
515 (put 'smtp-response-error 'error-conditions '(smtp-response-error smtp-error error))
516
517 (defun smtp-response-error (response)
518   (signal 'smtp-response-error response))
519
520 (defun smtp-read-response (connection)
521   (let ((decoder
522          (smtp-connection-decoder-internal connection))
523         (response-continue t)
524         response)
525     (while response-continue
526       (goto-char smtp-read-point)
527       (while (not (search-forward smtp-end-of-line nil t))
528         (accept-process-output (smtp-connection-process-internal connection))
529         (goto-char smtp-read-point))
530       (if decoder
531           (let ((string (buffer-substring smtp-read-point (- (point) 2))))
532             (delete-region smtp-read-point (point))
533             (insert (funcall decoder string) smtp-end-of-line)))
534       (setq response
535             (nconc response
536                    (list (buffer-substring
537                           (+ 4 smtp-read-point)
538                           (- (point) 2)))))
539       (goto-char
540        (prog1 smtp-read-point
541          (setq smtp-read-point (point))))
542       (if (looking-at "[1-5][0-9][0-9] ")
543           (setq response (cons (read (point-marker)) response)
544                 response-continue nil)))
545     response))
546
547 (defun smtp-send-command (connection command)
548   (save-excursion
549     (let ((process
550            (smtp-connection-process-internal connection))
551           (encoder
552            (smtp-connection-encoder-internal connection)))
553       (set-buffer (process-buffer process))
554       (goto-char (point-max))
555       (setq command (concat command smtp-end-of-line))
556       (insert command)
557       (setq smtp-read-point (point))
558       (if encoder
559           (setq command (funcall encoder command)))
560       (process-send-string process command))))
561
562 (defun smtp-send-data (connection data)
563   (let ((process
564          (smtp-connection-process-internal connection))
565         (encoder
566          (smtp-connection-encoder-internal connection)))
567     ;; Escape "." at start of a line.
568     (if (eq (string-to-char data) ?.)
569         (setq data (concat "." data smtp-end-of-line))
570       (setq data (concat data smtp-end-of-line)))
571     (if encoder
572         (setq data (funcall encoder data)))
573     (process-send-string process data)))
574
575 (defun smtp-deduce-address-list (smtp-text-buffer header-start header-end)
576   "Get address list suitable for smtp RCPT TO:<address>."
577   (let ((simple-address-list "")
578         this-line
579         this-line-end
580         addr-regexp
581         (smtp-address-buffer (generate-new-buffer " *smtp-mail*")))
582     (unwind-protect
583         (save-excursion
584           ;;
585           (set-buffer smtp-address-buffer)
586           (setq case-fold-search t)
587           (erase-buffer)
588           (insert (save-excursion
589                     (set-buffer smtp-text-buffer)
590                     (buffer-substring-no-properties header-start header-end)))
591           (goto-char (point-min))
592           ;; RESENT-* fields should stop processing of regular fields.
593           (save-excursion
594             (if (re-search-forward "^RESENT-TO:" header-end t)
595                 (setq addr-regexp
596                       "^\\(RESENT-TO:\\|RESENT-CC:\\|RESENT-BCC:\\)")
597               (setq addr-regexp  "^\\(TO:\\|CC:\\|BCC:\\)")))
598
599           (while (re-search-forward addr-regexp header-end t)
600             (replace-match "")
601             (setq this-line (match-beginning 0))
602             (forward-line 1)
603             ;; get any continuation lines.
604             (while (and (looking-at "^[ \t]+") (< (point) header-end))
605               (forward-line 1))
606             (setq this-line-end (point-marker))
607             (setq simple-address-list
608                   (concat simple-address-list " "
609                           (mail-strip-quoted-names
610                            (buffer-substring this-line this-line-end)))))
611           (erase-buffer)
612           (insert-string " ")
613           (insert-string simple-address-list)
614           (insert-string "\n")
615           ;; newline --> blank
616           (subst-char-in-region (point-min) (point-max) 10 ?  t)
617           ;; comma   --> blank
618           (subst-char-in-region (point-min) (point-max) ?, ?  t)
619           ;; tab     --> blank
620           (subst-char-in-region (point-min) (point-max)  9 ?  t)
621
622           (goto-char (point-min))
623           ;; tidyness in case hook is not robust when it looks at this
624           (while (re-search-forward "[ \t]+" header-end t) (replace-match " "))
625
626           (goto-char (point-min))
627           (let (recipient-address-list)
628             (while (re-search-forward " \\([^ ]+\\) " (point-max) t)
629               (backward-char 1)
630               (setq recipient-address-list
631                     (cons (buffer-substring (match-beginning 1) (match-end 1))
632                           recipient-address-list)))
633             recipient-address-list))
634       (kill-buffer smtp-address-buffer))))
635
636 (provide 'smtp)
637
638 ;;; smtp.el ends here