(gnus-xmas-group-startup-message): Bind the oort color as
[gnus] / lisp / mail-source.el
1 ;;; mail-source.el --- functions for fetching mail
2 ;; Copyright (C) 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
5 ;; Keywords: news, mail
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (eval-when-compile
29   (require 'cl)
30   (require 'imap)
31   (eval-when-compile (defvar display-time-mail-function)))
32 (eval-and-compile
33   (autoload 'pop3-movemail "pop3")
34   (autoload 'pop3-get-message-count "pop3")
35   (autoload 'nnheader-cancel-timer "nnheader")
36   (autoload 'nnheader-run-at-time "nnheader"))
37 (require 'format-spec)
38 (require 'mm-util)
39 (require 'message) ;; for `message-directory'
40
41 (defgroup mail-source nil
42   "The mail-fetching library."
43   :version "21.1"
44   :group 'gnus)
45
46 ;; Define these at compile time to avoid dragging in imap always.
47 (defconst mail-source-imap-authenticators
48   (eval-when-compile
49     (mapcar (lambda (a)
50               (list 'const (car a)))
51      imap-authenticator-alist)))
52 (defconst mail-source-imap-streams
53   (eval-when-compile
54     (mapcar (lambda (a)
55               (list 'const (car a)))
56      imap-stream-alist)))
57
58 (defcustom mail-sources nil
59   "*Where the mail backends will look for incoming mail.
60 This variable is a list of mail source specifiers.
61 See Info node `(gnus)Mail Source Specifiers'."
62   :group 'mail-source
63   :link '(custom-manual "(gnus)Mail Source Specifiers")
64   :type `(repeat
65           (choice :format "%[Value Menu%] %v"
66                   :value (file)
67                   (cons :tag "Spool file"
68                         (const :format "" file)
69                         (checklist :tag "Options" :greedy t
70                                    (group :inline t
71                                           (const :format "" :value :path)
72                                           file)))
73                   (cons :tag "Several files in a directory"
74                         (const :format "" directory)
75                         (checklist :tag "Options" :greedy t
76                                    (group :inline t
77                                           (const :format "" :value :path)
78                                           (directory :tag "Path"))
79                                    (group :inline t
80                                           (const :format "" :value :suffix)
81                                           (string :tag "Suffix"))
82                                    (group :inline t
83                                           (const :format "" :value :predicate)
84                                           (function :tag "Predicate"))
85                                    (group :inline t
86                                           (const :format "" :value :prescript)
87                                           (choice :tag "Prescript"
88                                                   :value nil
89                                                   (string :format "%v")
90                                                   (function :format "%v")))
91                                    (group :inline t
92                                           (const :format "" :value :postscript)
93                                           (choice :tag "Postscript"
94                                                   :value nil
95                                                   (string :format "%v")
96                                                   (function :format "%v")))
97                                    (group :inline t
98                                           (const :format "" :value :plugged)
99                                           (boolean :tag "Plugged"))))
100                   (cons :tag "POP3 server"
101                         (const :format "" pop)
102                         (checklist :tag "Options" :greedy t
103                                    (group :inline t
104                                           (const :format "" :value :server)
105                                           (string :tag "Server"))
106                                    (group :inline t
107                                           (const :format "" :value :port)
108                                           (choice :tag "Port"
109                                                   :value "pop3"
110                                                   (number :format "%v")
111                                                   (string :format "%v")))
112                                    (group :inline t
113                                           (const :format "" :value :user)
114                                           (string :tag "User"))
115                                    (group :inline t
116                                           (const :format "" :value :password)
117                                           (string :tag "Password"))
118                                    (group :inline t
119                                           (const :format "" :value :program)
120                                           (string :tag "Program"))
121                                    (group :inline t
122                                           (const :format "" :value :prescript)
123                                           (choice :tag "Prescript"
124                                                   :value nil
125                                                   (string :format "%v")
126                                                   (function :format "%v")))
127                                    (group :inline t
128                                           (const :format "" :value :postscript)
129                                           (choice :tag "Postscript"
130                                                   :value nil
131                                                   (string :format "%v")
132                                                   (function :format "%v")))
133                                    (group :inline t
134                                           (const :format "" :value :function)
135                                           (function :tag "Function"))
136                                    (group :inline t
137                                           (const :format ""
138                                                  :value :authentication)
139                                           (choice :tag "Authentication"
140                                                   :value apop
141                                                   (const password)
142                                                   (const apop)))
143                                    (group :inline t
144                                           (const :format "" :value :plugged)
145                                           (boolean :tag "Plugged"))))
146                   (cons :tag "Maildir (qmail, postfix...)"
147                         (const :format "" maildir)
148                         (checklist :tag "Options" :greedy t
149                                    (group :inline t
150                                           (const :format "" :value :path)
151                                           (directory :tag "Path"))
152                                    (group :inline t
153                                           (const :format "" :value :plugged)
154                                           (boolean :tag "Plugged"))))
155                   (cons :tag "IMAP server"
156                         (const :format "" imap)
157                         (checklist :tag "Options" :greedy t
158                                    (group :inline t
159                                           (const :format "" :value :server)
160                                           (string :tag "Server"))
161                                    (group :inline t
162                                           (const :format "" :value :port)
163                                           (choice :tag "Port"
164                                                   :value 143
165                                                   number string))
166                                    (group :inline t
167                                           (const :format "" :value :user)
168                                           (string :tag "User"))
169                                    (group :inline t
170                                           (const :format "" :value :password)
171                                           (string :tag "Password"))
172                                    (group :inline t
173                                           (const :format "" :value :stream)
174                                           (choice :tag "Stream"
175                                                   :value network
176                                                   ,@mail-source-imap-streams))
177                                    (group :inline t
178                                           (const :format "" :value :program)
179                                           (string :tag "Program"))
180                                    (group :inline t
181                                           (const :format ""
182                                                  :value :authenticator)
183                                           (choice :tag "Authenticator"
184                                                   :value login
185                                                   ,@mail-source-imap-authenticators))
186                                    (group :inline t
187                                           (const :format "" :value :mailbox)
188                                           (string :tag "Mailbox"
189                                                   :value "INBOX"))
190                                    (group :inline t
191                                           (const :format "" :value :predicate)
192                                           (string :tag "Predicate"
193                                                   :value "UNSEEN UNDELETED"))
194                                    (group :inline t
195                                           (const :format "" :value :fetchflag)
196                                           (string :tag "Fetchflag"
197                                                   :value  "\\Deleted"))
198                                    (group :inline t
199                                           (const :format ""
200                                                  :value :dontexpunge)
201                                           (boolean :tag "Dontexpunge"))
202                                    (group :inline t
203                                           (const :format "" :value :plugged)
204                                           (boolean :tag "Plugged"))))
205                   (cons :tag "Webmail server"
206                         (const :format "" webmail)
207                         (checklist :tag "Options" :greedy t
208                                    (group :inline t
209                                          (const :format "" :value :subtype)
210                                          ;; Should be generated from
211                                          ;; `webmail-type-definition', but we
212                                          ;; can't require webmail without W3.
213                                          (choice :tag "Subtype"
214                                                  :value hotmail
215                                                  (const hotmail)
216                                                  (const yahoo)
217                                                  (const netaddress)
218                                                  (const netscape)
219                                                  (const my-deja)))
220                                    (group :inline t
221                                           (const :format "" :value :user)
222                                           (string :tag "User"))
223                                    (group :inline t
224                                           (const :format "" :value :password)
225                                           (string :tag "Password"))
226                                    (group :inline t
227                                           (const :format ""
228                                                  :value :dontexpunge)
229                                           (boolean :tag "Dontexpunge"))
230                                    (group :inline t
231                                           (const :format "" :value :plugged)
232                                           (boolean :tag "Plugged")))))))
233
234 (defcustom mail-source-primary-source nil
235   "*Primary source for incoming mail.
236 If non-nil, this maildrop will be checked periodically for new mail."
237   :group 'mail-source
238   :type 'sexp)
239
240 (defcustom mail-source-flash t
241   "*If non-nil, flash periodically when mail is available."
242   :group 'mail-source
243   :type 'boolean)
244
245 (defcustom mail-source-crash-box "~/.emacs-mail-crash-box"
246   "File where mail will be stored while processing it."
247   :group 'mail-source
248   :type 'file)
249
250 (defcustom mail-source-directory message-directory
251   "Directory where files (if any) will be stored."
252   :group 'mail-source
253   :type 'directory)
254
255 (defcustom mail-source-default-file-modes 384
256   "Set the mode bits of all new mail files to this integer."
257   :group 'mail-source
258   :type 'integer)
259
260 (defcustom mail-source-delete-incoming nil
261   "*If non-nil, delete incoming files after handling."
262   :group 'mail-source
263   :type 'boolean)
264
265 (defcustom mail-source-incoming-file-prefix "Incoming"
266   "Prefix for file name for storing incoming mail"
267   :group 'mail-source
268   :type 'string)
269
270 (defcustom mail-source-report-new-mail-interval 5
271   "Interval in minutes between checks for new mail."
272   :group 'mail-source
273   :type 'number)
274
275 (defcustom mail-source-idle-time-delay 5
276   "Number of idle seconds to wait before checking for new mail."
277   :group 'mail-source
278   :type 'number)
279
280 (defcustom mail-source-movemail-program nil
281   "If non-nil, name of program for fetching new mail."
282   :group 'mail-source
283   :type '(choice (const nil) string))
284
285 ;;; Internal variables.
286
287 (defvar mail-source-string ""
288   "A dynamically bound string that says what the current mail source is.")
289
290 (defvar mail-source-new-mail-available nil
291   "Flag indicating when new mail is available.")
292
293 (eval-and-compile
294   (defvar mail-source-common-keyword-map
295     '((:plugged))
296     "Mapping from keywords to default values.
297 Common keywords should be listed here.")
298
299   (defvar mail-source-keyword-map
300     '((file
301        (:prescript)
302        (:prescript-delay)
303        (:postscript)
304        (:path (or (getenv "MAIL")
305                   (expand-file-name (user-login-name) rmail-spool-directory))))
306       (directory
307        (:prescript)
308        (:prescript-delay)
309        (:postscript)
310        (:path)
311        (:suffix ".spool")
312        (:predicate identity))
313       (pop
314        (:prescript)
315        (:prescript-delay)
316        (:postscript)
317        (:server (getenv "MAILHOST"))
318        (:port 110)
319        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
320        (:program)
321        (:function)
322        (:password)
323        (:authentication password))
324       (maildir
325        (:path (or (getenv "MAILDIR") "~/Maildir/"))
326        (:subdirs ("new" "cur"))
327        (:function))
328       (imap
329        (:server (getenv "MAILHOST"))
330        (:port)
331        (:stream)
332        (:program)
333        (:authentication)
334        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
335        (:password)
336        (:mailbox "INBOX")
337        (:predicate "UNSEEN UNDELETED")
338        (:fetchflag "\\Deleted")
339        (:dontexpunge))
340       (webmail
341        (:subtype hotmail)
342        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
343        (:password)
344        (:dontexpunge)
345        (:authentication password)))
346     "Mapping from keywords to default values.
347 All keywords that can be used must be listed here."))
348
349 (defvar mail-source-fetcher-alist
350   '((file mail-source-fetch-file)
351     (directory mail-source-fetch-directory)
352     (pop mail-source-fetch-pop)
353     (maildir mail-source-fetch-maildir)
354     (imap mail-source-fetch-imap)
355     (webmail mail-source-fetch-webmail))
356   "A mapping from source type to fetcher function.")
357
358 (defvar mail-source-password-cache nil)
359
360 (defvar mail-source-plugged t)
361
362 ;;; Functions
363
364 (eval-and-compile
365   (defun mail-source-strip-keyword (keyword)
366     "Strip the leading colon off the KEYWORD."
367     (intern (substring (symbol-name keyword) 1))))
368
369 (eval-and-compile
370   (defun mail-source-bind-1 (type)
371     (let* ((defaults (cdr (assq type mail-source-keyword-map)))
372            default bind)
373       (while (setq default (pop defaults))
374         (push (list (mail-source-strip-keyword (car default))
375                     nil)
376               bind))
377       bind)))
378
379 (defmacro mail-source-bind (type-source &rest body)
380   "Return a `let' form that binds all variables in source TYPE.
381 TYPE-SOURCE is a list where the first element is the TYPE, and
382 the second variable is the SOURCE.
383 At run time, the mail source specifier SOURCE will be inspected,
384 and the variables will be set according to it.  Variables not
385 specified will be given default values.
386
387 After this is done, BODY will be executed in the scope
388 of the `let' form.
389
390 The variables bound and their default values are described by
391 the `mail-source-keyword-map' variable."
392   `(let ,(mail-source-bind-1 (car type-source))
393      (mail-source-set-1 ,(cadr type-source))
394      ,@body))
395
396 (put 'mail-source-bind 'lisp-indent-function 1)
397 (put 'mail-source-bind 'edebug-form-spec '(sexp body))
398
399 (defun mail-source-set-1 (source)
400   (let* ((type (pop source))
401          (defaults (cdr (assq type mail-source-keyword-map)))
402          default value keyword)
403     (while (setq default (pop defaults))
404       (set (mail-source-strip-keyword (setq keyword (car default)))
405            (if (setq value (plist-get source keyword))
406                (mail-source-value value)
407              (mail-source-value (cadr default)))))))
408
409 (eval-and-compile
410   (defun mail-source-bind-common-1 ()
411     (let* ((defaults mail-source-common-keyword-map)
412            default bind)
413       (while (setq default (pop defaults))
414         (push (list (mail-source-strip-keyword (car default))
415                     nil)
416               bind))
417       bind)))
418
419 (defun mail-source-set-common-1 (source)
420   (let* ((type (pop source))
421          (defaults mail-source-common-keyword-map)
422          (defaults-1 (cdr (assq type mail-source-keyword-map)))
423          default value keyword)
424     (while (setq default (pop defaults))
425       (set (mail-source-strip-keyword (setq keyword (car default)))
426            (if (setq value (plist-get source keyword))
427                (mail-source-value value)
428              (if (setq value (assq  keyword defaults-1))
429                  (mail-source-value (cadr value))
430                (mail-source-value (cadr default))))))))
431
432 (defmacro mail-source-bind-common (source &rest body)
433   "Return a `let' form that binds all common variables.
434 See `mail-source-bind'."
435   `(let ,(mail-source-bind-common-1)
436      (mail-source-set-common-1 source)
437      ,@body))
438
439 (put 'mail-source-bind-common 'lisp-indent-function 1)
440 (put 'mail-source-bind-common 'edebug-form-spec '(sexp body))
441
442 (defun mail-source-value (value)
443   "Return the value of VALUE."
444   (cond
445    ;; String
446    ((stringp value)
447     value)
448    ;; Function
449    ((and (listp value)
450          (functionp (car value)))
451     (eval value))
452    ;; Just return the value.
453    (t
454     value)))
455
456 (defun mail-source-fetch (source callback)
457   "Fetch mail from SOURCE and call CALLBACK zero or more times.
458 CALLBACK will be called with the name of the file where (some of)
459 the mail from SOURCE is put.
460 Return the number of files that were found."
461   (mail-source-bind-common source
462     (if (or mail-source-plugged plugged)
463         (save-excursion
464           (let ((function (cadr (assq (car source) mail-source-fetcher-alist)))
465                 (found 0))
466             (unless function
467               (error "%S is an invalid mail source specification" source))
468             ;; If there's anything in the crash box, we do it first.
469             (when (file-exists-p mail-source-crash-box)
470               (message "Processing mail from %s..." mail-source-crash-box)
471               (setq found (mail-source-callback
472                            callback mail-source-crash-box)))
473             (+ found
474                (if (or debug-on-quit debug-on-error)
475                    (funcall function source callback)
476                  (condition-case err
477                      (funcall function source callback)
478                    (error
479                     (unless (yes-or-no-p
480                              (format "Mail source %s error (%s).  Continue? "
481                                      (if (memq ':password source)
482                                          (let ((s (copy-sequence source)))
483                                            (setcar (cdr (memq ':password s)) 
484                                                    "********")
485                                            s)
486                                        source)
487                                      (cadr err)))
488                       (error "Cannot get new mail"))
489                     0)))))))))
490
491 (eval-and-compile
492   (if (fboundp 'make-temp-file)
493       (defalias 'mail-source-make-complex-temp-name 'make-temp-file)
494     (defun mail-source-make-complex-temp-name (prefix)
495       (let ((newname (make-temp-name prefix))
496             (newprefix prefix))
497         (while (file-exists-p newname)
498           (setq newprefix (concat newprefix "x"))
499           (setq newname (make-temp-name newprefix)))
500         newname))))
501
502 (defun mail-source-callback (callback info)
503   "Call CALLBACK on the mail file, and then remove the mail file.
504 Pass INFO on to CALLBACK."
505   (if (or (not (file-exists-p mail-source-crash-box))
506           (zerop (nth 7 (file-attributes mail-source-crash-box))))
507       (progn
508         (when (file-exists-p mail-source-crash-box)
509           (delete-file mail-source-crash-box))
510         0)
511     (prog1
512         (funcall callback mail-source-crash-box info)
513       (when (file-exists-p mail-source-crash-box)
514         ;; Delete or move the incoming mail out of the way.
515         (if mail-source-delete-incoming
516             (delete-file mail-source-crash-box)
517           (let ((incoming
518                  (mail-source-make-complex-temp-name
519                   (expand-file-name
520                    mail-source-incoming-file-prefix
521                    mail-source-directory))))
522             (unless (file-exists-p (file-name-directory incoming))
523               (make-directory (file-name-directory incoming) t))
524             (rename-file mail-source-crash-box incoming t)))))))
525
526 (defun mail-source-movemail (from to)
527   "Move FROM to TO using movemail."
528   (if (not (file-writable-p to))
529       (error "Can't write to crash box %s.  Not moving mail" to)
530     (let ((to (file-truename (expand-file-name to)))
531           errors result)
532       (setq to (file-truename to)
533             from (file-truename from))
534       ;; Set TO if have not already done so, and rename or copy
535       ;; the file FROM to TO if and as appropriate.
536       (cond
537        ((file-exists-p to)
538         ;; The crash box exists already.
539         t)
540        ((not (file-exists-p from))
541         ;; There is no inbox.
542         (setq to nil))
543        ((zerop (nth 7 (file-attributes from)))
544         ;; Empty file.
545         (setq to nil))
546        (t
547         ;; If getting from mail spool directory, use movemail to move
548         ;; rather than just renaming, so as to interlock with the
549         ;; mailer.
550         (unwind-protect
551             (save-excursion
552               (setq errors (generate-new-buffer " *mail source loss*"))
553               (let ((default-directory "/"))
554                 (setq result
555                       (apply
556                        'call-process
557                        (append
558                         (list
559                          (or mail-source-movemail-program
560                              (expand-file-name "movemail" exec-directory))
561                          nil errors nil from to)))))
562               (when (file-exists-p to)
563                 (set-file-modes to mail-source-default-file-modes))
564               (if (and (or (not (buffer-modified-p errors))
565                            (zerop (buffer-size errors)))
566                        (zerop result))
567                   ;; No output => movemail won.
568                   t
569                 (set-buffer errors)
570                 ;; There may be a warning about older revisions.  We
571                 ;; ignore that.
572                 (goto-char (point-min))
573                 (if (search-forward "older revision" nil t)
574                     t
575                   ;; Probably a real error.
576                   (subst-char-in-region (point-min) (point-max) ?\n ?\  )
577                   (goto-char (point-max))
578                   (skip-chars-backward " \t")
579                   (delete-region (point) (point-max))
580                   (goto-char (point-min))
581                   (when (looking-at "movemail: ")
582                     (delete-region (point-min) (match-end 0)))
583                   ;; Result may be a signal description string.
584                   (unless (yes-or-no-p
585                            (format "movemail: %s (%s return).  Continue? "
586                                    (buffer-string) result))
587                     (error "%s" (buffer-string)))
588                   (setq to nil)))))))
589       (when (and errors
590                  (buffer-name errors))
591         (kill-buffer errors))
592       ;; Return whether we moved successfully or not.
593       to)))
594
595 (defun mail-source-movemail-and-remove (from to)
596   "Move FROM to TO using movemail, then remove FROM if empty."
597   (or (not (mail-source-movemail from to))
598       (not (zerop (nth 7 (file-attributes from))))
599       (delete-file from)))
600
601 (defvar mail-source-read-passwd nil)
602 (defun mail-source-read-passwd (prompt &rest args)
603   "Read a password using PROMPT.
604 If ARGS, PROMPT is used as an argument to `format'."
605   (let ((prompt
606          (if args
607              (apply 'format prompt args)
608            prompt)))
609     (unless mail-source-read-passwd
610       (if (or (fboundp 'read-passwd) (load "passwd" t))
611           (setq mail-source-read-passwd 'read-passwd)
612         (unless (fboundp 'ange-ftp-read-passwd)
613           (autoload 'ange-ftp-read-passwd "ange-ftp"))
614         (setq mail-source-read-passwd 'ange-ftp-read-passwd)))
615     (funcall mail-source-read-passwd prompt)))
616
617 (defun mail-source-fetch-with-program (program)
618   (zerop (call-process shell-file-name nil nil nil
619                        shell-command-switch program)))
620
621 (defun mail-source-run-script (script spec &optional delay)
622   (when script
623     (if (functionp script)
624         (funcall script)
625       (mail-source-call-script
626        (format-spec script spec))))
627   (when delay
628     (sleep-for delay)))
629
630 (defun mail-source-call-script (script)
631   (let ((background nil))
632     (when (string-match "& *$" script)
633       (setq script (substring script 0 (match-beginning 0))
634             background 0))
635     (call-process shell-file-name nil background nil
636                   shell-command-switch script)))
637
638 ;;;
639 ;;; Different fetchers