Require 'poe and 'pcustom.
[elisp/flim.git] / smtp.el
1 ;;; smtp.el --- basic functions to send mail with SMTP server
2
3 ;; Copyright (C) 1995, 1996, 1998 Free Software Foundation, Inc.
4
5 ;; Author: Tomoji Kagatani <kagatani@rbc.ncl.omron.co.jp>
6 ;;      Simon Leinen <simon@switch.ch> (ESMTP support)
7 ;;      MORIOKA Tomohiko <tomo@m17n.org> (separate smtp.el from smtpmail.el)
8 ;;      Shuhei KOBAYASHI <shuhei@aqua.ocn.ne.jp>
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 ;;; Code:
29
30 (require 'poe)
31 (require 'poem)
32 (require 'pcustom)
33 (require 'mail-utils)                   ; mail-strip-quoted-names
34
35 (eval-when-compile (require 'cl))       ; push
36
37 (defgroup smtp nil
38   "SMTP protocol for sending mail."
39   :group 'mail)
40
41 (defcustom smtp-default-server nil
42   "*Specify default SMTP server."
43   :type '(choice (const nil) string)
44   :group 'smtp)
45
46 (defcustom smtp-server (or (getenv "SMTPSERVER") smtp-default-server)
47   "*The name of the host running SMTP server.  It can also be a function
48 called from `smtp-via-smtp' with arguments SENDER and RECIPIENTS."
49   :type '(choice (string :tag "Name")
50                  (function :tag "Function"))
51   :group 'smtp)
52
53 (defcustom smtp-service "smtp"
54   "*SMTP service port number. \"smtp\" or 25."
55   :type '(choice (integer :tag "25" 25)
56                  (string :tag "smtp" "smtp"))
57   :group 'smtp)
58
59 (defcustom smtp-use-8bitmime t
60   "*If non-nil, use ESMTP 8BITMIME if available."
61   :type 'boolean
62   :group 'smtp)
63
64 (defcustom smtp-local-domain nil
65   "*Local domain name without a host name.
66 If the function (system-name) returns the full internet address,
67 don't define this value."
68   :type '(choice (const nil) string)
69   :group 'smtp)
70
71 (defvar smtp-debug-info nil)
72 (defvar smtp-read-point nil)
73
74 (defun smtp-make-fqdn ()
75   "Return user's fully qualified domain name."
76   (let ((system-name (system-name)))
77     (cond
78      (smtp-local-domain
79       (concat system-name "." smtp-local-domain))
80      ((string-match "[^.]\\.[^.]" system-name)
81       system-name)
82      (t
83       (error "Cannot generate valid FQDN. Set `smtp-local-domain' correctly.")))))
84
85 (defun smtp-via-smtp (sender recipients smtp-text-buffer)
86   (let ((server (if (functionp smtp-server)
87                     (funcall smtp-server sender recipients)
88                   smtp-server))
89         process response extensions)
90     (save-excursion
91       (set-buffer
92        (get-buffer-create
93         (format "*trace of SMTP session to %s*" server)))
94       (erase-buffer)
95       (make-local-variable 'smtp-read-point)
96       (setq smtp-read-point (point-min))
97
98       (unwind-protect
99           (catch 'done
100             (setq process (open-network-stream-as-binary
101                            "SMTP" (current-buffer) server smtp-service))
102             (or process (throw 'done nil))
103
104             (set-process-filter process 'smtp-process-filter)
105
106             ;; Greeting
107             (setq response (smtp-read-response process))
108             (if (or (null (car response))
109                     (not (integerp (car response)))
110                     (>= (car response) 400))
111                 (throw 'done (car (cdr response))))
112
113             ;; EHLO
114             (smtp-send-command process
115                                (format "EHLO %s" (smtp-make-fqdn)))
116             (setq response (smtp-read-response process))
117             (if (or (null (car response))
118                     (not (integerp (car response)))
119                     (>= (car response) 400))
120                 (progn
121                   ;; HELO
122                   (smtp-send-command process
123                                      (format "HELO %s" (smtp-make-fqdn)))
124                   (setq response (smtp-read-response process))
125                   (if (or (null (car response))
126                           (not (integerp (car response)))
127                           (>= (car response) 400))
128                       (throw 'done (car (cdr response)))))
129               (let ((extension-lines (cdr (cdr response))))
130                 (while extension-lines
131                   (push (intern (downcase (substring (car extension-lines) 4)))
132                         extensions)
133                   (setq extension-lines (cdr extension-lines)))))
134
135             ;; ONEX --- One message transaction only (sendmail extension?)
136             (if (or (memq 'onex extensions)
137                     (memq 'xone extensions))
138                 (progn
139                   (smtp-send-command process "ONEX")
140                   (setq response (smtp-read-response process))
141                   (if (or (null (car response))
142                           (not (integerp (car response)))
143                           (>= (car response) 400))
144                       (throw 'done (car (cdr response))))))
145
146             ;; VERB --- Verbose (sendmail extension?)
147             (if (and smtp-debug-info
148                      (or (memq 'verb extensions)
149                          (memq 'xvrb extensions)))
150                 (progn
151                   (smtp-send-command process "VERB")
152                   (setq response (smtp-read-response process))
153                   (if (or (null (car response))
154                           (not (integerp (car response)))
155                           (>= (car response) 400))
156                       (throw 'done (car (cdr response))))))
157
158             ;; XUSR --- Initial (user) submission (sendmail extension?)
159             (if (memq 'xusr extensions)
160                 (progn
161                   (smtp-send-command process "XUSR")
162                   (setq response (smtp-read-response process))
163                   (if (or (null (car response))
164                           (not (integerp (car response)))
165                           (>= (car response) 400))
166                       (throw 'done (car (cdr response))))))
167
168             ;; MAIL FROM:<sender>
169             (smtp-send-command
170              process
171              (format "MAIL FROM:<%s>%s%s"
172                      sender
173                      ;; SIZE --- Message Size Declaration (RFC1870)
174                      (if (memq 'size extensions)
175                          (format " SIZE=%d"
176                                  (save-excursion
177                                    (set-buffer smtp-text-buffer)
178                                    (+ (- (point-max) (point-min))
179                                       ;; Add one byte for each change-of-line
180                                       ;; because or CR-LF representation:
181                                       (count-lines (point-min) (point-max))
182                                       ;; For some reason, an empty line is
183                                       ;; added to the message.  Maybe this
184                                       ;; is a bug, but it can't hurt to add
185                                       ;; those two bytes anyway:
186                                       2)))
187                        "")
188                      ;; 8BITMIME --- 8bit-MIMEtransport (RFC1652)
189                      (if (and (memq '8bitmime extensions)
190                               smtp-use-8bitmime)
191                          " BODY=8BITMIME"
192                        "")))
193             (setq response (smtp-read-response process))
194             (if (or (null (car response))
195                     (not (integerp (car response)))
196                     (>= (car response) 400))
197                 (throw 'done (car (cdr response))))
198
199             ;; RCPT TO:<recipient>
200             (while recipients
201               (smtp-send-command process
202                                  (format "RCPT TO:<%s>" (car recipients)))
203               (setq recipients (cdr recipients))
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
210             ;; DATA
211             (smtp-send-command process "DATA")
212             (setq response (smtp-read-response process))
213             (if (or (null (car response))
214                     (not (integerp (car response)))
215                     (>= (car response) 400))
216                 (throw 'done (car (cdr response))))
217
218             ;; Mail contents
219             (smtp-send-data process smtp-text-buffer)
220
221             ;; DATA end "."
222             (smtp-send-command process ".")
223             (setq response (smtp-read-response process))
224             (if (or (null (car response))
225                     (not (integerp (car response)))
226                     (>= (car response) 400))
227                 (throw 'done (car (cdr response))))
228
229             t)
230
231         (if (and process
232                  (eq (process-status process) 'open))
233             (progn
234               ;; QUIT
235               (smtp-send-command process "QUIT")
236               (smtp-read-response process)
237               (delete-process process)))))))
238
239 (defun smtp-process-filter (process output)
240   (save-excursion
241     (set-buffer (process-buffer process))
242     (goto-char (point-max))
243     (insert output)))
244
245 (defun smtp-read-response (process)
246   (let ((case-fold-search nil)
247         (response-strings nil)
248         (response-continue t)
249         (return-value '(nil ()))
250         match-end)
251
252     (while response-continue
253       (goto-char smtp-read-point)
254       (while (not (search-forward "\r\n" nil t))
255         (accept-process-output process)
256         (goto-char smtp-read-point))
257
258       (setq match-end (point))
259       (setq response-strings
260             (cons (buffer-substring smtp-read-point (- match-end 2))
261                   response-strings))
262         
263       (goto-char smtp-read-point)
264       (if (looking-at "[0-9]+ ")
265           (let ((begin (match-beginning 0))
266                 (end (match-end 0)))
267             (if smtp-debug-info
268                 (message "%s" (car response-strings)))
269
270             (setq smtp-read-point match-end)
271
272             ;; ignore lines that start with "0"
273             (if (looking-at "0[0-9]+ ")
274                 nil
275               (setq response-continue nil)
276               (setq return-value
277                     (cons (string-to-int
278                            (buffer-substring begin end))
279                           (nreverse response-strings)))))
280         
281         (if (looking-at "[0-9]+-")
282             (progn (if smtp-debug-info
283                      (message "%s" (car response-strings)))
284                    (setq smtp-read-point match-end)
285                    (setq response-continue t))
286           (progn
287             (setq smtp-read-point match-end)
288             (setq response-continue nil)
289             (setq return-value
290                   (cons nil (nreverse response-strings)))))))
291     (setq smtp-read-point match-end)
292     return-value))
293
294 (defun smtp-send-command (process command)
295   (goto-char (point-max))
296   (insert command "\r\n")
297   (setq smtp-read-point (point))
298   (process-send-string process command)
299   (process-send-string process "\r\n"))
300
301 (defun smtp-send-data-1 (process data)
302   (goto-char (point-max))
303   (if smtp-debug-info
304       (insert data "\r\n"))
305   (setq smtp-read-point (point))
306   ;; Escape "." at start of a line.
307   (if (eq (string-to-char data) ?.)
308       (process-send-string process "."))
309   (process-send-string process data)
310   (process-send-string process "\r\n"))
311
312 (defun smtp-send-data (process buffer)
313   (let ((data-continue t)
314         (sending-data nil)
315         this-line
316         this-line-end)
317
318     (save-excursion
319       (set-buffer buffer)
320       (goto-char (point-min)))
321
322     (while data-continue
323       (save-excursion
324         (set-buffer buffer)
325         (beginning-of-line)
326         (setq this-line (point))
327         (end-of-line)
328         (setq this-line-end (point))
329         (setq sending-data nil)
330         (setq sending-data (buffer-substring this-line this-line-end))
331         (if (or (/= (forward-line 1) 0) (eobp))
332             (setq data-continue nil)))
333
334       (smtp-send-data-1 process sending-data))))
335
336 (defun smtp-deduce-address-list (smtp-text-buffer header-start header-end)
337   "Get address list suitable for smtp RCPT TO:<address>."
338   (let ((case-fold-search t)
339         (simple-address-list "")
340         this-line
341         this-line-end
342         addr-regexp
343         (smtp-address-buffer (generate-new-buffer " *smtp-mail*")))
344     (unwind-protect
345         (save-excursion
346           ;;
347           (set-buffer smtp-address-buffer)
348           (erase-buffer)
349           (insert (save-excursion
350                     (set-buffer smtp-text-buffer)
351                     (buffer-substring-no-properties header-start header-end)))
352           (goto-char (point-min))
353           ;; RESENT-* fields should stop processing of regular fields.
354           (save-excursion
355             (if (re-search-forward "^RESENT-TO:" header-end t)
356                 (setq addr-regexp
357                       "^\\(RESENT-TO:\\|RESENT-CC:\\|RESENT-BCC:\\)")
358               (setq addr-regexp  "^\\(TO:\\|CC:\\|BCC:\\)")))
359
360           (while (re-search-forward addr-regexp header-end t)
361             (replace-match "")
362             (setq this-line (match-beginning 0))
363             (forward-line 1)
364             ;; get any continuation lines.
365             (while (and (looking-at "^[ \t]+") (< (point) header-end))
366               (forward-line 1))
367             (setq this-line-end (point-marker))
368             (setq simple-address-list
369                   (concat simple-address-list " "
370                           (mail-strip-quoted-names
371                            (buffer-substring this-line this-line-end)))))
372           (erase-buffer)
373           (insert-string " ")
374           (insert-string simple-address-list)
375           (insert-string "\n")
376           ;; newline --> blank
377           (subst-char-in-region (point-min) (point-max) 10 ?  t)
378           ;; comma   --> blank
379           (subst-char-in-region (point-min) (point-max) ?, ?  t)
380           ;; tab     --> blank
381           (subst-char-in-region (point-min) (point-max)  9 ?  t)
382
383           (goto-char (point-min))
384           ;; tidyness in case hook is not robust when it looks at this
385           (while (re-search-forward "[ \t]+" header-end t) (replace-match " "))
386
387           (goto-char (point-min))
388           (let (recipient-address-list)
389             (while (re-search-forward " \\([^ ]+\\) " (point-max) t)
390               (backward-char 1)
391               (setq recipient-address-list
392                     (cons (buffer-substring (match-beginning 1) (match-end 1))
393                           recipient-address-list)))
394             recipient-address-list))
395       (kill-buffer smtp-address-buffer))))
396
397 (provide 'smtp)
398
399 ;;; smtp.el ends here