Modify header.
[elisp/gnus.git-] / lisp / smtp.el
1 ;;; smtp.el --- basic functions of SMTP protocol (RFC 821)
2
3 ;; Copyright (C) 1995, 1996, 1998 Free Software Foundation, Inc.
4
5 ;; Author: Tomoji Kagatani <kagatani@rbc.ncl.omron.co.jp>
6 ;; ESMTP support: Simon Leinen <simon@switch.ch>
7 ;; Keywords: SMTP, mail
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
25
26 ;;; Code:
27
28 (defgroup smtp nil
29   "SMTP protocol for sending mail."
30   :group 'mail)
31
32 (defcustom smtp-default-server nil
33   "*Specify default SMTP server."
34   :type '(choice (const nil) string)
35   :group 'smtp)
36
37 (defcustom smtp-server 
38   (or (getenv "SMTPSERVER") smtp-default-server)
39   "*The name of the host running SMTP server."
40   :type '(choice (const nil) string)
41   :group 'smtp)
42
43 (defcustom smtp-service 25
44   "*SMTP service port number. smtp or 25 ."
45   :type 'integer
46   :group 'smtp)
47
48 (defcustom smtp-local-domain nil
49   "*Local domain name without a host name.
50 If the function (system-name) returns the full internet address,
51 don't define this value."
52   :type '(choice (const nil) string)
53   :group 'smtp)
54
55 (defcustom smtp-debug-info nil
56   "*smtp debug info printout. messages and process buffer."
57   :type 'boolean
58   :group 'smtp)
59
60 (defcustom smtp-coding-system 'binary
61   "*Coding-system for SMTP output."
62   :type 'coding-system
63   :group 'smtp)
64
65
66 (defun smtp-fqdn ()
67   (if smtp-local-domain
68       (concat (system-name) "." smtp-local-domain)
69     (system-name)))
70
71 (defun smtp-via-smtp (recipient smtp-text-buffer)
72   (let ((process nil)
73         (host smtp-server)
74         (port smtp-service)
75         response-code
76         greeting
77         process-buffer
78         (supported-extensions '())
79         (coding-system-for-read smtp-coding-system)
80         (coding-system-for-write smtp-coding-system))
81     (unwind-protect
82         (catch 'done
83           ;; get or create the trace buffer
84           (setq process-buffer
85                 (get-buffer-create
86                  (format "*trace of SMTP session to %s*" host)))
87
88           ;; clear the trace buffer of old output
89           (save-excursion
90             (set-buffer process-buffer)
91             (erase-buffer))
92
93           ;; open the connection to the server
94           (setq process (open-network-stream "SMTP" process-buffer host port))
95           (and (null process) (throw 'done nil))
96
97           ;; set the send-filter
98           (set-process-filter process 'smtp-process-filter)
99
100           (save-excursion
101             (set-buffer process-buffer)
102             (make-local-variable 'smtp-read-point)
103             (setq smtp-read-point (point-min))
104
105             (if (or (null (car (setq greeting (smtp-read-response process))))
106                     (not (integerp (car greeting)))
107                     (>= (car greeting) 400))
108                 (throw 'done nil)
109               )
110
111             ;; EHLO
112             (smtp-send-command process (format "EHLO %s" (smtp-fqdn)))
113
114             (if (or (null (car (setq response-code (smtp-read-response process))))
115                     (not (integerp (car response-code)))
116                     (>= (car response-code) 400))
117                 (progn
118                   ;; HELO
119                   (smtp-send-command process (format "HELO %s" (smtp-fqdn)))
120
121                   (if (or (null (car (setq response-code (smtp-read-response process))))
122                           (not (integerp (car response-code)))
123                           (>= (car response-code) 400))
124                       (throw 'done nil)))
125               (let ((extension-lines (cdr (cdr response-code))))
126                 (while extension-lines
127                   (let ((name (intern (downcase (substring (car extension-lines) 4)))))
128                     (and name
129                          (cond ((memq name '(verb xvrb 8bitmime onex xone
130                                                   expn size dsn etrn
131                                                   help xusr))
132                                 (setq supported-extensions
133                                       (cons name supported-extensions)))
134                                (t (message "unknown extension %s"
135                                            name)))))
136                   (setq extension-lines (cdr extension-lines)))))
137
138             (if (or (member 'onex supported-extensions)
139                     (member 'xone supported-extensions))
140                 (progn
141                   (smtp-send-command process (format "ONEX"))
142                   (if (or (null (car (setq response-code (smtp-read-response process))))
143                           (not (integerp (car response-code)))
144                           (>= (car response-code) 400))
145                       (throw 'done nil))))
146
147             (if (and smtp-debug-info
148                      (or (member 'verb supported-extensions)
149                          (member 'xvrb supported-extensions)))
150                 (progn
151                   (smtp-send-command process (format "VERB"))
152                   (if (or (null (car (setq response-code (smtp-read-response process))))
153                           (not (integerp (car response-code)))
154                           (>= (car response-code) 400))
155                       (throw 'done nil))))
156
157             (if (member 'xusr supported-extensions)
158                 (progn
159                   (smtp-send-command process (format "XUSR"))
160                   (if (or (null (car (setq response-code (smtp-read-response process))))
161                           (not (integerp (car response-code)))
162                           (>= (car response-code) 400))
163                       (throw 'done nil))))
164
165             ;; MAIL FROM: <sender>
166             (let ((size-part
167                    (if (member 'size supported-extensions)
168                        (format " SIZE=%d"
169                                (save-excursion
170                                  (set-buffer smtp-text-buffer)
171                                  ;; size estimate:
172                                  (+ (- (point-max) (point-min))
173                                     ;; Add one byte for each change-of-line
174                                     ;; because or CR-LF representation:
175                                     (count-lines (point-min) (point-max))
176                                     ;; For some reason, an empty line is
177                                     ;; added to the message.  Maybe this
178                                     ;; is a bug, but it can't hurt to add
179                                     ;; those two bytes anyway:
180                                     2)))
181                      ""))
182                   (body-part
183                    (if (member '8bitmime supported-extensions)
184                        ;; FIXME:
185                        ;; Code should be added here that transforms
186                        ;; the contents of the message buffer into
187                        ;; something the receiving SMTP can handle.
188                        ;; For a receiver that supports 8BITMIME, this
189                        ;; may mean converting BINARY to BASE64, or
190                        ;; adding Content-Transfer-Encoding and the
191                        ;; other MIME headers.  The code should also
192                        ;; return an indication of what encoding the
193                        ;; message buffer is now, i.e. ASCII or
194                        ;; 8BITMIME.
195                        (if nil
196                            " BODY=8BITMIME"
197                          "")
198                      "")))
199 ;             (smtp-send-command process (format "MAIL FROM:%s@%s" (user-login-name) (smtp-fqdn)))
200               (smtp-send-command process (format "MAIL FROM: <%s>%s%s"
201                                                      user-mail-address
202                                                      size-part
203                                                      body-part))
204
205               (if (or (null (car (setq response-code (smtp-read-response process))))
206                       (not (integerp (car response-code)))
207                       (>= (car response-code) 400))
208                   (throw 'done nil)
209                 ))
210             
211             ;; RCPT TO: <recipient>
212             (let ((n 0))
213               (while (not (null (nth n recipient)))
214                 (smtp-send-command process (format "RCPT TO: <%s>" (nth n recipient)))
215                 (setq n (1+ n))
216
217                 (setq response-code (smtp-read-response process))
218                 (if (or (null (car response-code))
219                         (not (integerp (car response-code)))
220                         (>= (car response-code) 400))
221                     (throw 'done nil)
222                   )
223                 ))
224             
225             ;; DATA
226             (smtp-send-command process "DATA")
227
228             (if (or (null (car (setq response-code (smtp-read-response process))))
229                     (not (integerp (car response-code)))
230                     (>= (car response-code) 400))
231                 (throw 'done nil)
232               )
233
234             ;; Mail contents
235             (smtp-send-data process smtp-text-buffer)
236
237             ;;DATA end "."
238             (smtp-send-command process ".")
239
240             (if (or (null (car (setq response-code (smtp-read-response process))))
241                     (not (integerp (car response-code)))
242                     (>= (car response-code) 400))
243                 (throw 'done nil)
244               )
245
246             ;;QUIT
247 ;           (smtp-send-command process "QUIT")
248 ;           (and (null (car (smtp-read-response process)))
249 ;                (throw 'done nil))
250             t ))
251       (if process
252           (save-excursion
253             (set-buffer (process-buffer process))
254             (smtp-send-command process "QUIT")
255             (smtp-read-response process)
256
257 ;           (if (or (null (car (setq response-code (smtp-read-response process))))
258 ;                   (not (integerp (car response-code)))
259 ;                   (>= (car response-code) 400))
260 ;               (throw 'done nil)
261 ;             )
262             (delete-process process))))))
263
264 (defun smtp-process-filter (process output)
265   (save-excursion
266     (set-buffer (process-buffer process))
267     (goto-char (point-max))
268     (insert output)))
269
270 (defun smtp-read-response (process)
271   (let ((case-fold-search nil)
272         (response-strings nil)
273         (response-continue t)
274         (return-value '(nil ()))
275         match-end)
276
277     (while response-continue
278       (goto-char smtp-read-point)
279       (while (not (search-forward "\r\n" nil t))
280         (accept-process-output process)
281         (goto-char smtp-read-point))
282
283       (setq match-end (point))
284       (setq response-strings
285             (cons (buffer-substring smtp-read-point (- match-end 2))
286                   response-strings))
287         
288       (goto-char smtp-read-point)
289       (if (looking-at "[0-9]+ ")
290           (let ((begin (match-beginning 0))
291                 (end (match-end 0)))
292             (if smtp-debug-info
293                 (message "%s" (car response-strings)))
294
295             (setq smtp-read-point match-end)
296
297             ;; ignore lines that start with "0"
298             (if (looking-at "0[0-9]+ ")
299                 nil
300               (setq response-continue nil)
301               (setq return-value
302                     (cons (string-to-int 
303                            (buffer-substring begin end)) 
304                           (nreverse response-strings)))))
305         
306         (if (looking-at "[0-9]+-")
307             (progn (if smtp-debug-info
308                      (message "%s" (car response-strings)))
309                    (setq smtp-read-point match-end)
310                    (setq response-continue t))
311           (progn
312             (setq smtp-read-point match-end)
313             (setq response-continue nil)
314             (setq return-value 
315                   (cons nil (nreverse response-strings)))
316             )
317           )))
318     (setq smtp-read-point match-end)
319     return-value))
320
321 (defun smtp-send-command (process command)
322   (goto-char (point-max))
323   (if (= (aref command 0) ?P)
324       (insert "PASS <omitted>\r\n")
325     (insert command "\r\n"))
326   (setq smtp-read-point (point))
327   (process-send-string process command)
328   (process-send-string process "\r\n"))
329
330 (defun smtp-send-data-1 (process data)
331   (goto-char (point-max))
332
333   (if smtp-debug-info
334       (insert data "\r\n"))
335
336   (setq smtp-read-point (point))
337   ;; Escape "." at start of a line
338   (if (eq (string-to-char data) ?.)
339       (process-send-string process "."))
340   (process-send-string process data)
341   (process-send-string process "\r\n")
342   )
343
344 (defun smtp-send-data (process buffer)
345   (let
346       ((data-continue t)
347        (sending-data nil)
348        this-line
349        this-line-end)
350
351     (save-excursion
352       (set-buffer buffer)
353       (goto-char (point-min)))
354
355     (while data-continue
356       (save-excursion
357         (set-buffer buffer)
358         (beginning-of-line)
359         (setq this-line (point))
360         (end-of-line)
361         (setq this-line-end (point))
362         (setq sending-data nil)
363         (setq sending-data (buffer-substring this-line this-line-end))
364         (if (/= (forward-line 1) 0)
365             (setq data-continue nil)))
366
367       (smtp-send-data-1 process sending-data)
368       )
369     )
370   )
371
372 (defun smtp-deduce-address-list (smtp-text-buffer header-start header-end)
373   "Get address list suitable for smtp RCPT TO: <address>."
374   (require 'mail-utils)  ;; pick up mail-strip-quoted-names
375   (let ((case-fold-search t)
376         (simple-address-list "")
377         this-line
378         this-line-end
379         addr-regexp)
380     
381     (unwind-protect
382         (save-excursion
383           ;;
384           (set-buffer smtp-address-buffer) (erase-buffer)
385           (insert-buffer-substring smtp-text-buffer
386                                    header-start header-end)
387           (goto-char (point-min))
388           ;; RESENT-* fields should stop processing of regular fields.
389           (save-excursion
390             (if (re-search-forward "^RESENT-TO:" header-end t)
391                 (setq addr-regexp "^\\(RESENT-TO:\\|RESENT-CC:\\|RESENT-BCC:\\)")
392               (setq addr-regexp  "^\\(TO:\\|CC:\\|BCC:\\)")))
393
394           (while (re-search-forward addr-regexp header-end t)
395             (replace-match "")
396             (setq this-line (match-beginning 0))
397             (forward-line 1)
398             ;; get any continuation lines
399             (while (and (looking-at "^[ \t]+") (< (point) header-end))
400               (forward-line 1))
401             (setq this-line-end (point-marker))
402             (setq simple-address-list
403                   (concat simple-address-list " "
404                           (mail-strip-quoted-names (buffer-substring this-line this-line-end))))
405             )
406           (erase-buffer)
407           (insert-string " ")
408           (insert-string simple-address-list)
409           (insert-string "\n")
410           (subst-char-in-region (point-min) (point-max) 10 ?  t);; newline --> blank
411           (subst-char-in-region (point-min) (point-max) ?, ?  t);; comma   --> blank
412           (subst-char-in-region (point-min) (point-max)  9 ?  t);; tab     --> blank
413
414           (goto-char (point-min))
415           ;; tidyness in case hook is not robust when it looks at this
416           (while (re-search-forward "[ \t]+" header-end t) (replace-match " "))
417
418           (goto-char (point-min))
419           (let (recipient-address-list)
420             (while (re-search-forward " \\([^ ]+\\) " (point-max) t)
421               (backward-char 1)
422               (setq recipient-address-list (cons (buffer-substring (match-beginning 1) (match-end 1))
423                                                  recipient-address-list))
424               )
425             (setq smtp-recipient-address-list recipient-address-list))
426
427           )
428       )
429     )
430   )
431
432 (defun smtp-do-bcc (header-end)
433   "Delete BCC: and their continuation lines from the header area.
434 There may be multiple BCC: lines, and each may have arbitrarily
435 many continuation lines."
436   (let ((case-fold-search t))
437     (save-excursion
438       (goto-char (point-min))
439       ;; iterate over all BCC: lines
440       (while (re-search-forward "^BCC:" header-end t)
441         (delete-region (match-beginning 0) (progn (forward-line 1) (point)))
442         ;; get rid of any continuation lines
443         (while (and (looking-at "^[ \t].*\n") (< (point) header-end))
444           (replace-match ""))
445         )
446       ) ;; save-excursion
447     ) ;; let
448   )
449
450 (provide 'smtp)
451
452 ;;; smtp.el ends here