Import Oort Gnus v0.09.
[elisp/gnus.git-] / 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
640 ;;;
641
642 (defun mail-source-fetch-file (source callback)
643   "Fetcher for single-file sources."
644   (mail-source-bind (file source)
645     (mail-source-run-script
646      prescript (format-spec-make ?t mail-source-crash-box)
647      prescript-delay)
648     (let ((mail-source-string (format "file:%s" path)))
649       (if (mail-source-movemail path mail-source-crash-box)
650           (prog1
651               (mail-source-callback callback path)
652             (mail-source-run-script
653              postscript (format-spec-make ?t mail-source-crash-box)))
654         0))))
655
656 (defun mail-source-fetch-directory (source callback)
657   "Fetcher for directory sources."
658   (mail-source-bind (directory source)
659     (mail-source-run-script
660      prescript (format-spec-make ?t path)
661      prescript-delay)
662     (let ((found 0)
663           (mail-source-string (format "directory:%s" path)))
664       (dolist (file (directory-files
665                      path t (concat (regexp-quote suffix) "$")))
666         (when (and (file-regular-p file)
667                    (funcall predicate file)
668                    (mail-source-movemail file mail-source-crash-box))
669           (incf found (mail-source-callback callback file))))
670       (mail-source-run-script
671        postscript (format-spec-make ?t path))
672       found)))
673
674 (defun mail-source-fetch-pop (source callback)
675   "Fetcher for single-file sources."
676   (mail-source-bind (pop source)
677     (mail-source-run-script
678      prescript
679      (format-spec-make ?p password ?t mail-source-crash-box
680                        ?s server ?P port ?u user)
681      prescript-delay)
682     (let ((from (format "%s:%s:%s" server user port))
683           (mail-source-string (format "pop:%s@%s" user server))
684           result)
685       (when (eq authentication 'password)
686         (setq password
687               (or password
688                   (cdr (assoc from mail-source-password-cache))
689                   (mail-source-read-passwd
690                    (format "Password for %s at %s: " user server)))))
691       (when server
692         (setenv "MAILHOST" server))
693       (setq result
694             (cond
695              (program
696               (mail-source-fetch-with-program
697                (format-spec
698                 program
699                 (format-spec-make ?p password ?t mail-source-crash-box
700                                   ?s server ?P port ?u user))))
701              (function
702               (funcall function mail-source-crash-box))
703              ;; The default is to use pop3.el.
704              (t
705               (let ((pop3-password password)
706                     (pop3-maildrop user)
707                     (pop3-mailhost server)
708                     (pop3-port port)
709                     (pop3-authentication-scheme
710                      (if (eq authentication 'apop) 'apop 'pass)))
711                 (if (or debug-on-quit debug-on-error)
712                     (save-excursion (pop3-movemail mail-source-crash-box))
713                   (condition-case err
714                       (save-excursion (pop3-movemail mail-source-crash-box))
715                     (error
716                      ;; We nix out the password in case the error
717                      ;; was because of a wrong password being given.
718                      (setq mail-source-password-cache
719                            (delq (assoc from mail-source-password-cache)
720                                  mail-source-password-cache))
721                      (signal (car err) (cdr err)))))))))
722       (if result
723           (progn
724             (when (eq authentication 'password)
725               (unless (assoc from mail-source-password-cache)
726                 (push (cons from password) mail-source-password-cache)))
727             (prog1
728                 (mail-source-callback callback server)
729               ;; Update display-time's mail flag, if relevant.
730               (if (equal source mail-source-primary-source)
731                   (setq mail-source-new-mail-available nil))
732               (mail-source-run-script
733                postscript
734                (format-spec-make ?p password ?t mail-source-crash-box
735                                  ?s server ?P port ?u user))))
736         ;; We nix out the password in case the error
737         ;; was because of a wrong password being given.
738         (setq mail-source-password-cache
739               (delq (assoc from mail-source-password-cache)
740                     mail-source-password-cache))
741         0))))
742
743 (defun mail-source-check-pop (source)
744   "Check whether there is new mail."
745   (mail-source-bind (pop source)
746     (let ((from (format "%s:%s:%s" server user port))
747           (mail-source-string (format "pop:%s@%s" user server))
748           result)
749       (when (eq authentication 'password)
750         (setq password
751               (or password
752                   (cdr (assoc from mail-source-password-cache))
753                   (mail-source-read-passwd
754                    (format "Password for %s at %s: " user server))))
755         (unless (assoc from mail-source-password-cache)
756           (push (cons from password) mail-source-password-cache)))
757       (when server
758         (setenv "MAILHOST" server))
759       (setq result
760             (cond
761              ;; No easy way to check whether mail is waiting for these.
762              (program)
763              (function)
764              ;; The default is to use pop3.el.
765              (t
766               (let ((pop3-password password)
767                     (pop3-maildrop user)
768                     (pop3-mailhost server)
769                     (pop3-port port)
770                     (pop3-authentication-scheme
771                      (if (eq authentication 'apop) 'apop 'pass)))
772                 (if (or debug-on-quit debug-on-error)
773                     (save-excursion (pop3-get-message-count))
774                   (condition-case err
775                       (save-excursion (pop3-get-message-count))
776                     (error
777                      ;; We nix out the password in case the error
778                      ;; was because of a wrong password being given.
779                      (setq mail-source-password-cache
780                            (delq (assoc from mail-source-password-cache)
781                                  mail-source-password-cache))
782                      (signal (car err) (cdr err)))))))))
783       (if result
784           ;; Inform display-time that we have new mail.
785           (setq mail-source-new-mail-available (> result 0))
786         ;; We nix out the password in case the error
787         ;; was because of a wrong password being given.
788         (setq mail-source-password-cache
789               (delq (assoc from mail-source-password-cache)
790                     mail-source-password-cache)))
791       result)))
792
793 (defun mail-source-touch-pop ()
794   "Open and close a POP connection shortly.
795 POP server should be defined in `mail-source-primary-source' (which is
796 preferred) or `mail-sources'.  You may use it for the POP-before-SMTP
797 authentication.  To do that, you need to set the option
798 `message-send-mail-function' to `message-smtpmail-send-it' and put the
799 following line in .gnus file:
800
801 \(add-hook 'message-send-mail-hook 'mail-source-touch-pop)
802 "
803   (let ((sources (if mail-source-primary-source
804                      (list mail-source-primary-source)
805                    mail-sources)))
806     (while sources
807       (if (eq 'pop (car (car sources)))
808           (mail-source-check-pop (car sources)))
809       (setq sources (cdr sources)))))
810
811 (defun mail-source-new-mail-p ()
812   "Handler for `display-time' to indicate when new mail is available."
813   ;; Flash (ie. ring the visible bell) if mail is available.
814   (if (and mail-source-flash mail-source-new-mail-available)
815       (let ((visible-bell t))
816         (ding)))
817   ;; Only report flag setting; flag is updated on a different schedule.
818   mail-source-new-mail-available)
819
820
821 (defvar mail-source-report-new-mail nil)
822 (defvar mail-source-report-new-mail-timer nil)
823 (defvar mail-source-report-new-mail-idle-timer nil)
824
825 (eval-when-compile
826   (if (featurep 'xemacs)
827       (require 'itimer)
828     (require 'timer)))
829
830 (defun mail-source-start-idle-timer ()
831   ;; Start our idle timer if necessary, so we delay the check until the
832   ;; user isn't typing.
833   (unless mail-source-report-new-mail-idle-timer
834     (setq mail-source-report-new-mail-idle-timer
835           (run-with-idle-timer
836            mail-source-idle-time-delay
837            nil
838            (lambda ()
839              (unwind-protect
840                  (mail-source-check-pop mail-source-primary-source)
841                (setq mail-source-report-new-mail-idle-timer nil)))))
842     ;; Since idle timers created when Emacs is already in the idle
843     ;; state don't get activated until Emacs _next_ becomes idle, we
844     ;; need to force our timer to be considered active now.  We do
845     ;; this by being naughty and poking the timer internals directly
846     ;; (element 0 of the vector is nil if the timer is active).
847     (aset mail-source-report-new-mail-idle-timer 0 nil)))
848
849 (defun mail-source-report-new-mail (arg)
850   "Toggle whether to report when new mail is available.
851 This only works when `display-time' is enabled."
852   (interactive "P")
853   (if (not mail-source-primary-source)
854       (error "Need to set `mail-source-primary-source' to check for new mail"))
855   (let ((on (if (null arg)
856                 (not mail-source-report-new-mail)
857               (> (prefix-numeric-value arg) 0))))
858     (setq mail-source-report-new-mail on)
859     (and mail-source-report-new-mail-timer
860          (nnheader-cancel-timer mail-source-report-new-mail-timer))
861     (and mail-source-report-new-mail-idle-timer
862          (nnheader-cancel-timer mail-source-report-new-mail-idle-timer))
863     (setq mail-source-report-new-mail-timer nil)
864     (setq mail-source-report-new-mail-idle-timer nil)
865     (if on
866         (progn
867           (require 'time)
868           ;; display-time-mail-function is an Emacs 21 feature.
869           (setq display-time-mail-function #'mail-source-new-mail-p)
870           ;; Set up the main timer.
871           (setq mail-source-report-new-mail-timer
872                 (nnheader-run-at-time
873                  (* 60 mail-source-report-new-mail-interval)
874                  (* 60 mail-source-report-new-mail-interval)
875                  #'mail-source-start-idle-timer))
876           ;; When you get new mail, clear "Mail" from the mode line.
877           (add-hook 'nnmail-post-get-new-mail-hook
878                     'display-time-event-handler)
879           (message "Mail check enabled"))
880       (setq display-time-mail-function nil)
881       (remove-hook 'nnmail-post-get-new-mail-hook
882                    'display-time-event-handler)
883       (message "Mail check disabled"))))
884
885 (defun mail-source-fetch-maildir (source callback)
886   "Fetcher for maildir sources."
887   (mail-source-bind (maildir source)
888     (let ((found 0)
889           mail-source-string)
890       (unless (string-match "/$" path)
891         (setq path (concat path "/")))
892       (dolist (subdir subdirs)
893         (when (file-directory-p (concat path subdir))
894           (setq mail-source-string (format "maildir:%s%s" path subdir))
895           (dolist (file (directory-files (concat path subdir) t))
896             (when (and (not (file-directory-p file))
897                        (not (if function
898                                 (funcall function file mail-source-crash-box)
899                               (let ((coding-system-for-write
900                                      mm-text-coding-system)
901                                     (coding-system-for-read
902                                      mm-text-coding-system))
903                                 (with-temp-file mail-source-crash-box
904                                   (insert-file-contents file)
905                                   (goto-char (point-min))
906 ;;;                               ;; Unix mail format
907 ;;;                               (unless (looking-at "\n*From ")
908 ;;;                                 (insert "From maildir "
909 ;;;                                         (current-time-string) "\n"))
910 ;;;                               (while (re-search-forward "^From " nil t)
911 ;;;                                 (replace-match ">From "))
912 ;;;                               (goto-char (point-max))
913 ;;;                               (insert "\n\n")
914                                   ;; MMDF mail format
915                                   (insert "\001\001\001\001\n"))
916                                 (delete-file file)))))
917               (incf found (mail-source-callback callback file))))))
918       found)))
919
920 (eval-and-compile
921   (autoload 'imap-open "imap")
922   (autoload 'imap-authenticate "imap")
923   (autoload 'imap-mailbox-select "imap")
924   (autoload 'imap-mailbox-unselect "imap")
925   (autoload 'imap-mailbox-close "imap")
926   (autoload 'imap-search "imap")
927   (autoload 'imap-fetch "imap")
928   (autoload 'imap-close "imap")
929   (autoload 'imap-error-text "imap")
930   (autoload 'imap-message-flags-add "imap")
931   (autoload 'imap-list-to-message-set "imap")
932   (autoload 'imap-range-to-message-set "imap")
933   (autoload 'nnheader-ms-strip-cr "nnheader"))
934
935 (defvar mail-source-imap-file-coding-system 'binary
936   "Coding system for the crashbox made by `mail-source-fetch-imap'.")
937
938 (defun mail-source-fetch-imap (source callback)
939   "Fetcher for imap sources."
940   (mail-source-bind (imap source)
941     (let ((from (format "%s:%s:%s" server user port))
942           (found 0)
943           (buf (get-buffer-create (generate-new-buffer-name " *imap source*")))
944           (mail-source-string (format "imap:%s:%s" server mailbox))
945           (imap-shell-program (or (list program) imap-shell-program))
946           remove)
947       (if (and (imap-open server port stream authentication buf)
948                (imap-authenticate
949                 user (or (cdr (assoc from mail-source-password-cache))
950                          password) buf)
951                (imap-mailbox-select mailbox nil buf))
952           (let ((coding-system-for-write mail-source-imap-file-coding-system)
953                 str)
954             (with-temp-file mail-source-crash-box
955               ;; Avoid converting 8-bit chars from inserted strings to
956               ;; multibyte.
957               (mm-disable-multibyte)
958               ;; remember password
959               (with-current-buffer buf
960                 (when (or imap-password
961                           (assoc from mail-source-password-cache))
962                   (push (cons from imap-password) mail-source-password-cache)))
963               ;; if predicate is nil, use all uids
964               (dolist (uid (imap-search (or predicate "1:*") buf))
965                 (when (setq str
966                             (if (imap-capability 'IMAP4rev1 buf)
967                                 (caddar (imap-fetch uid "BODY.PEEK[]"
968                                                     'BODYDETAIL nil buf))
969                               (imap-fetch uid "RFC822.PEEK" 'RFC822 nil buf)))
970                   (push uid remove)
971                   (insert "From imap " (current-time-string) "\n")
972                   (save-excursion
973                     (insert str "\n\n"))
974                   (while (re-search-forward "^From " nil t)
975                     (replace-match ">From "))
976                   (goto-char (point-max))))
977               (nnheader-ms-strip-cr))
978             (incf found (mail-source-callback callback server))
979             (when (and remove fetchflag)
980               (imap-message-flags-add
981                (imap-range-to-message-set (gnus-compress-sequence remove))
982                fetchflag nil buf))
983             (if dontexpunge
984                 (imap-mailbox-unselect buf)
985               (imap-mailbox-close nil buf))
986             (imap-close buf))
987         (imap-close buf)
988         ;; We nix out the password in case the error
989         ;; was because of a wrong password being given.
990         (setq mail-source-password-cache
991               (delq (assoc from mail-source-password-cache)
992                     mail-source-password-cache))
993         (error (imap-error-text buf)))
994       (kill-buffer buf)
995       found)))
996
997 (eval-and-compile
998   (autoload 'webmail-fetch "webmail"))
999
1000 (defun mail-source-fetch-webmail (source callback)
1001   "Fetch for webmail source."
1002   (mail-source-bind (webmail source)
1003     (let ((mail-source-string (format "webmail:%s:%s" subtype user))
1004           (webmail-newmail-only dontexpunge)
1005           (webmail-move-to-trash-can (not dontexpunge)))
1006       (when (eq authentication 'password)
1007         (setq password
1008               (or password
1009                   (cdr (assoc (format "webmail:%s:%s" subtype user)
1010                               mail-source-password-cache))
1011                   (mail-source-read-passwd
1012                    (format "Password for %s at %s: " user subtype))))
1013         (when (and password
1014                    (not (assoc (format "webmail:%s:%s" subtype user)
1015                                mail-source-password-cache)))
1016           (push (cons (format "webmail:%s:%s" subtype user) password)
1017                 mail-source-password-cache)))
1018       (webmail-fetch mail-source-crash-box subtype user password)
1019       (mail-source-callback callback (symbol-name subtype)))))
1020
1021 (provide 'mail-source)
1022
1023 ;;; mail-source.el ends here