* smtp.el (smtp-send-command): Add new optional argument
[elisp/flim.git] / smtp.el
1 ;;; smtp.el --- basic functions to send mail with SMTP server
2
3 ;; Copyright (C) 1995, 1996, 1998, 1999 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 ;;      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
12
13 ;; This file is part of FLIM (Faithful Library about Internet Message).
14
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.
19
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.
24
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.
29
30 ;;; Code:
31
32 (require 'poe)
33 (require 'poem)
34 (require 'pcustom)
35 (require 'mail-utils)                   ; mail-strip-quoted-names
36
37 (eval-and-compile
38   (autoload 'starttls-open-stream "starttls")
39   (autoload 'starttls-negotiate "starttls")
40   (autoload 'sasl-cram-md5 "sasl")
41   (autoload 'sasl-plain "sasl"))
42                        
43 (eval-when-compile (require 'cl))       ; push
44
45 (defgroup smtp nil
46   "SMTP protocol for sending mail."
47   :group 'mail)
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.  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"))
59   :group 'smtp)
60
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"))
65   :group 'smtp)
66
67 (defcustom smtp-use-8bitmime t
68   "*If non-nil, use ESMTP 8BITMIME if available."
69   :type 'boolean
70   :group 'smtp)
71
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)
77   :group 'smtp)
78
79 (defcustom smtp-debug-info nil
80   "*smtp debug info printout. messages and process buffer."
81   :type 'boolean
82   :group 'smtp)
83
84 (defcustom smtp-notify-success nil
85   "*If non-nil, notification for successful mail delivery is returned 
86  to user (RFC1891)."
87   :type 'boolean
88   :group 'smtp)
89
90 (defcustom smtp-authentication-type nil
91   "*SMTP authentication mechanism (RFC2554)."
92   :type 'symbol
93   :group 'smtp)
94
95 (defvar smtp-authentication-user nil)
96 (defvar smtp-authentication-passphrase nil)
97
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     ))
104
105 (defcustom smtp-connection-type nil
106   "*SMTP connection type."
107   :type '(choice (const nil) (const :tag "TLS" starttls))
108   :group 'smtp)
109  
110 (defvar smtp-read-point nil)
111
112 (defun smtp-make-fqdn ()
113   "Return user's fully qualified domain name."
114   (let ((system-name (system-name)))
115     (cond
116      (smtp-local-domain
117       (concat system-name "." smtp-local-domain))
118      ((string-match "[^.]\\.[^.]" system-name)
119       system-name)
120      (t
121       (error "Cannot generate valid FQDN. Set `smtp-local-domain' correctly.")))))
122
123 (defun smtp-via-smtp (sender recipients smtp-text-buffer)
124   (let ((server (if (functionp smtp-server)
125                     (funcall smtp-server sender recipients)
126                   smtp-server))
127         process response extensions)
128     (save-excursion
129       (set-buffer
130        (get-buffer-create
131         (format "*trace of SMTP session to %s*" server)))
132       (erase-buffer)
133       (make-local-variable 'smtp-read-point)
134       (setq smtp-read-point (point-min))
135
136       (unwind-protect
137           (catch 'done
138             (setq process 
139                   (if smtp-connection-type
140                       (starttls-open-stream
141                        "SMTP" (current-buffer) server smtp-service)
142                     (open-network-stream-as-binary
143                      "SMTP" (current-buffer) server smtp-service)))
144             (or process (throw 'done nil))
145
146             (set-process-filter process 'smtp-process-filter)
147
148             (if (eq smtp-connection-type 'force)
149                 (starttls-negotiate process))
150
151             ;; Greeting
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             ;; EHLO
159             (smtp-send-command process
160                                (format "EHLO %s" (smtp-make-fqdn)))
161             (setq response (smtp-read-response process))
162             (if (or (null (car response))
163                     (not (integerp (car response)))
164                     (>= (car response) 400))
165                 (progn
166                   ;; HELO
167                   (smtp-send-command process
168                                      (format "HELO %s" (smtp-make-fqdn)))
169                   (setq response (smtp-read-response process))
170                   (if (or (null (car response))
171                           (not (integerp (car response)))
172                           (>= (car response) 400))
173                       (throw 'done (car (cdr response)))))
174               (let ((extension-lines (cdr (cdr response)))
175                     extension)
176                 (while extension-lines
177                   (if (string-match
178                        "^auth "
179                        (setq extension
180                              (downcase (substring (car extension-lines) 4))))
181                       (while (string-match "\\([^ ]+\\)" extension (match-end 1))
182                         (push (intern (match-string 1 extension)) extensions))
183                     (push (intern extension) extensions))
184                   (setq extension-lines (cdr extension-lines)))))
185
186             ;; STARTTLS --- begin a TLS negotiation (RFC 2595)
187             (when (and smtp-connection-type 
188                        (null (eq smtp-connection-type 'force))
189                        (memq 'starttls extensions))
190               (smtp-send-command process "STARTTLS")
191               (setq response (smtp-read-response process))
192               (if (or (null (car response))
193                       (not (integerp (car response)))
194                       (>= (car response) 400))
195                   (throw 'done (car (cdr response))))
196               (starttls-negotiate process))
197
198             ;; AUTH --- SMTP Service Extension for Authentication (RFC2554)
199             (when smtp-authentication-type
200               (let ((auth (intern smtp-authentication-type)) method)
201                 (if (and 
202                      (memq auth extensions)
203                      (setq method (nth 1 (assq auth smtp-authentication-method-alist))))
204                     (funcall method process)
205                   (throw 'smtp-error
206                          (format "AUTH mechanism %s not available" auth)))))
207
208             ;; ONEX --- One message transaction only (sendmail extension?)
209 ;;          (if (or (memq 'onex extensions)
210 ;;                  (memq 'xone extensions))
211 ;;              (progn
212 ;;                (smtp-send-command process "ONEX")
213 ;;                (setq response (smtp-read-response process))
214 ;;                (if (or (null (car response))
215 ;;                        (not (integerp (car response)))
216 ;;                        (>= (car response) 400))
217 ;;                    (throw 'done (car (cdr response))))))
218
219             ;; VERB --- Verbose (sendmail extension?)
220 ;;          (if (and smtp-debug-info
221 ;;                   (or (memq 'verb extensions)
222 ;;                       (memq 'xvrb extensions)))
223 ;;              (progn
224 ;;                (smtp-send-command process "VERB")
225 ;;                (setq response (smtp-read-response process))
226 ;;                (if (or (null (car response))
227 ;;                        (not (integerp (car response)))
228 ;;                        (>= (car response) 400))
229 ;;                    (throw 'done (car (cdr response))))))
230
231             ;; XUSR --- Initial (user) submission (sendmail extension?)
232 ;;          (if (memq 'xusr extensions)
233 ;;              (progn
234 ;;                (smtp-send-command process "XUSR")
235 ;;                (setq response (smtp-read-response process))
236 ;;                (if (or (null (car response))
237 ;;                        (not (integerp (car response)))
238 ;;                        (>= (car response) 400))
239 ;;                    (throw 'done (car (cdr response))))))
240
241             ;; MAIL FROM:<sender>
242             (smtp-send-command
243              process
244              (format "MAIL FROM:<%s>%s%s"
245                      sender
246                      ;; SIZE --- Message Size Declaration (RFC1870)
247                      (if (memq 'size extensions)
248                          (format " SIZE=%d"
249                                  (save-excursion
250                                    (set-buffer smtp-text-buffer)
251                                    (+ (- (point-max) (point-min))
252                                       ;; Add one byte for each change-of-line
253                                       ;; because or CR-LF representation:
254                                       (count-lines (point-min) (point-max))
255                                       ;; For some reason, an empty line is
256                                       ;; added to the message.  Maybe this
257                                       ;; is a bug, but it can't hurt to add
258                                       ;; those two bytes anyway:
259                                       2)))
260                        "")
261                      ;; 8BITMIME --- 8bit-MIMEtransport (RFC1652)
262                      (if (and (memq '8bitmime extensions)
263                               smtp-use-8bitmime)
264                          " BODY=8BITMIME"
265                        "")))
266             (setq response (smtp-read-response process))
267             (if (or (null (car response))
268                     (not (integerp (car response)))
269                     (>= (car response) 400))
270                 (throw 'done (car (cdr response))))
271
272             ;; RCPT TO:<recipient>
273             (while recipients
274               (smtp-send-command process
275                                  (format
276                                   (if smtp-notify-success
277                                       "RCPT TO:<%s> NOTIFY=SUCCESS" 
278                                     "RCPT TO:<%s>")
279                                   (car recipients)))
280               (setq recipients (cdr recipients))
281               (setq response (smtp-read-response process))
282               (if (or (null (car response))
283                       (not (integerp (car response)))
284                       (>= (car response) 400))
285                   (throw 'done (car (cdr response)))))
286
287             ;; DATA
288             (smtp-send-command process "DATA")
289             (setq response (smtp-read-response process))
290             (if (or (null (car response))
291                     (not (integerp (car response)))
292                     (>= (car response) 400))
293                 (throw 'done (car (cdr response))))
294
295             ;; Mail contents
296             (smtp-send-data process smtp-text-buffer)
297
298             ;; DATA end "."
299             (smtp-send-command process ".")
300             (setq response (smtp-read-response process))
301             (if (or (null (car response))
302                     (not (integerp (car response)))
303                     (>= (car response) 400))
304                 (throw 'done (car (cdr response))))
305
306             t)
307
308         (if (and process
309                  (eq (process-status process) 'open))
310             (progn
311               ;; QUIT
312               (smtp-send-command process "QUIT")
313               (smtp-read-response process)
314               (delete-process process)))))))
315
316 (defun smtp-process-filter (process output)
317   (save-excursion
318     (set-buffer (process-buffer process))
319     (goto-char (point-max))
320     (insert output)))
321
322 (defun smtp-read-response (process)
323   (let ((case-fold-search nil)
324         (response-strings nil)
325         (response-continue t)
326         (return-value '(nil ()))
327         match-end)
328
329     (while response-continue
330       (goto-char smtp-read-point)
331       (while (not (search-forward "\r\n" nil t))
332         (accept-process-output process)
333         (goto-char smtp-read-point))
334
335       (setq match-end (point))
336       (setq response-strings
337             (cons (buffer-substring smtp-read-point (- match-end 2))
338                   response-strings))
339         
340       (goto-char smtp-read-point)
341       (if (looking-at "[0-9]+ ")
342           (let ((begin (match-beginning 0))
343                 (end (match-end 0)))
344             (if smtp-debug-info
345                 (message "%s" (car response-strings)))
346
347             (setq smtp-read-point match-end)
348
349             ;; ignore lines that start with "0"
350             (if (looking-at "0[0-9]+ ")
351                 nil
352               (setq response-continue nil)
353               (setq return-value
354                     (cons (string-to-int
355                            (buffer-substring begin end))
356                           (nreverse response-strings)))))
357         
358         (if (looking-at "[0-9]+-")
359             (progn (if smtp-debug-info
360                      (message "%s" (car response-strings)))
361                    (setq smtp-read-point match-end)
362                    (setq response-continue t))
363           (progn
364             (setq smtp-read-point match-end)
365             (setq response-continue nil)
366             (setq return-value
367                   (cons nil (nreverse response-strings)))))))
368     (setq smtp-read-point match-end)
369     return-value))
370
371 (defun smtp-send-command (process command &optional secure)
372   (goto-char (point-max))
373   (if secure
374       (insert "Here is insecure words.\r\n")
375     (insert command "\r\n"))
376   (setq smtp-read-point (point))
377   (process-send-string process command)
378   (process-send-string process "\r\n"))
379
380 (defun smtp-send-data-1 (process data)
381   (goto-char (point-max))
382   (if smtp-debug-info
383       (insert data "\r\n"))
384   (setq smtp-read-point (point))
385   ;; Escape "." at start of a line.
386   (if (eq (string-to-char data) ?.)
387       (process-send-string process "."))
388   (process-send-string process data)
389   (process-send-string process "\r\n"))
390
391 (defun smtp-send-data (process buffer)
392   (let ((data-continue t)
393         (sending-data nil)
394         this-line
395         this-line-end)
396
397     (save-excursion
398       (set-buffer buffer)
399       (goto-char (point-min)))
400
401     (while data-continue
402       (save-excursion
403         (set-buffer buffer)
404         (beginning-of-line)
405         (setq this-line (point))
406         (end-of-line)
407         (setq this-line-end (point))
408         (setq sending-data nil)
409         (setq sending-data (buffer-substring this-line this-line-end))
410         (if (or (/= (forward-line 1) 0) (eobp))
411             (setq data-continue nil)))
412
413       (smtp-send-data-1 process sending-data))))
414
415 (defun smtp-deduce-address-list (smtp-text-buffer header-start header-end)
416   "Get address list suitable for smtp RCPT TO:<address>."
417   (let ((case-fold-search t)
418         (simple-address-list "")
419         this-line
420         this-line-end
421         addr-regexp
422         (smtp-address-buffer (generate-new-buffer " *smtp-mail*")))
423     (unwind-protect
424         (save-excursion
425           ;;
426           (set-buffer smtp-address-buffer)
427           (erase-buffer)
428           (insert (save-excursion
429                     (set-buffer smtp-text-buffer)
430                     (buffer-substring-no-properties header-start header-end)))
431           (goto-char (point-min))
432           ;; RESENT-* fields should stop processing of regular fields.
433           (save-excursion
434             (if (re-search-forward "^RESENT-TO:" header-end t)
435                 (setq addr-regexp
436                       "^\\(RESENT-TO:\\|RESENT-CC:\\|RESENT-BCC:\\)")
437               (setq addr-regexp  "^\\(TO:\\|CC:\\|BCC:\\)")))
438
439           (while (re-search-forward addr-regexp header-end t)
440             (replace-match "")
441             (setq this-line (match-beginning 0))
442             (forward-line 1)
443             ;; get any continuation lines.
444             (while (and (looking-at "^[ \t]+") (< (point) header-end))
445               (forward-line 1))
446             (setq this-line-end (point-marker))
447             (setq simple-address-list
448                   (concat simple-address-list " "
449                           (mail-strip-quoted-names
450                            (buffer-substring this-line this-line-end)))))
451           (erase-buffer)
452           (insert-string " ")
453           (insert-string simple-address-list)
454           (insert-string "\n")
455           ;; newline --> blank
456           (subst-char-in-region (point-min) (point-max) 10 ?  t)
457           ;; comma   --> blank
458           (subst-char-in-region (point-min) (point-max) ?, ?  t)
459           ;; tab     --> blank
460           (subst-char-in-region (point-min) (point-max)  9 ?  t)
461
462           (goto-char (point-min))
463           ;; tidyness in case hook is not robust when it looks at this
464           (while (re-search-forward "[ \t]+" header-end t) (replace-match " "))
465
466           (goto-char (point-min))
467           (let (recipient-address-list)
468             (while (re-search-forward " \\([^ ]+\\) " (point-max) t)
469               (backward-char 1)
470               (setq recipient-address-list
471                     (cons (buffer-substring (match-beginning 1) (match-end 1))
472                           recipient-address-list)))
473             recipient-address-list))
474       (kill-buffer smtp-address-buffer))))
475
476 (defun smtp-auth-cram-md5 (process)
477   (let ((secure-word (copy-sequence smtp-authentication-passphrase))
478         response)
479     (smtp-send-command process "AUTH CRAM-MD5")
480     (setq response (smtp-read-response process))
481     (if (or (null (car response))
482             (not (integerp (car response)))
483             (>= (car response) 400))
484         (throw 'done (car (cdr response))))
485     (smtp-send-command
486      process
487      (setq secure-word (unwind-protect
488                            (sasl-cram-md5
489                             smtp-authentication-user secure-word
490                             (base64-decode-string
491                              (substring (car (cdr response)) 4)))
492                          (fillarray secure-word 0))
493            secure-word (unwind-protect
494                            (base64-encode-string secure-word)
495                          (fillarray secure-word 0))) t)
496     (fillarray secure-word 0)
497     (setq response (smtp-read-response process))
498     (if (or (null (car response))
499             (not (integerp (car response)))
500             (>= (car response) 400))
501         (throw 'done (car (cdr response))))))
502  
503 (defun smtp-auth-plain (process)
504   (let ((secure-word (copy-sequence smtp-authentication-passphrase))
505         response)
506     (smtp-send-command
507      process
508      (setq secure-word (unwind-protect
509                            (sasl-plain "" smtp-authentication-user secure-word)
510                          (fillarray secure-word 0))
511            secure-word (unwind-protect
512                            (base64-encode-string secure-word)
513                          (fillarray secure-word 0))
514            secure-word (unwind-protect
515                            (concat "AUTH PLAIN " secure-word)
516                          (fillarray secure-word 0))) t)
517     (fillarray secure-word 0)
518     (setq response (smtp-read-response process))
519     (if (or (null (car response))
520             (not (integerp (car response)))
521             (>= (car response) 400))
522         (throw 'done (car (cdr response))))))
523
524 (defun smtp-auth-login (process)
525   (let ((secure-word (copy-sequence smtp-authentication-passphrase))
526         response)
527     (smtp-send-command
528      process
529      (concat "AUTH LOGIN " smtp-authentication-user))
530     (setq response (smtp-read-response process))
531     (if (or (null (car response))
532             (not (integerp (car response)))
533             (>= (car response) 400))
534         (throw 'done (car (cdr response))))
535     (smtp-send-command
536      process
537      (setq secure-word (unwind-protect
538                            (base64-encode-string secure-word)
539                          (fillarray secure-word 0))) t)
540     (fillarray secure-word 0)
541     (setq response (smtp-read-response process))
542     (if (or (null (car response))
543             (not (integerp (car response)))
544             (>= (car response) 400))
545         (throw 'done (car (cdr response))))))
546
547 (defun smtp-auth-anonymous (process &optional token)
548   (let (response)
549     (smtp-send-command
550      process "AUTH ANONYMOUS")
551     (setq response (smtp-read-response process))
552     (if (or (null (car response))
553             (not (integerp (car response)))
554             (>= (car response) 400))
555         (throw 'done (car (cdr response))))
556     (smtp-send-command process
557                        (base64-encode-string 
558                         (or token
559                             user-mail-address
560                             "")))
561     (setq response (smtp-read-response process))
562     (if (or (null (car response))
563             (not (integerp (car response)))
564             (>= (car response) 400))
565         (throw 'done (car (cdr response))))))
566  
567 (provide 'smtp)
568
569 ;;; smtp.el ends here