T-gnus 6.15.16 (quimby) revision 00.
[elisp/gnus.git-] / lisp / mail-source.el
1 ;;; mail-source.el --- functions for fetching mail
2 ;; Copyright (C) 1999, 2000, 2001, 2002, 2003 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   (defvar pop3-leave-mail-on-server)
34   (autoload 'pop3-movemail "pop3")
35   (autoload 'pop3-get-message-count "pop3")
36   (autoload 'nnheader-cancel-timer "nnheader")
37   (autoload 'nnheader-run-at-time "nnheader"))
38 (require 'format-spec)
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-ignore-errors nil
235   "*Ignore errors when querying mail sources.
236 If nil, the user will be prompted when an error occurs.  If non-nil,
237 the error will be ignored.")
238
239 (defcustom mail-source-primary-source nil
240   "*Primary source for incoming mail.
241 If non-nil, this maildrop will be checked periodically for new mail."
242   :group 'mail-source
243   :type 'sexp)
244
245 (defcustom mail-source-flash t
246   "*If non-nil, flash periodically when mail is available."
247   :group 'mail-source
248   :type 'boolean)
249
250 (defcustom mail-source-crash-box "~/.emacs-mail-crash-box"
251   "File where mail will be stored while processing it."
252   :group 'mail-source
253   :type 'file)
254
255 (defcustom mail-source-directory message-directory
256   "Directory where files (if any) will be stored."
257   :group 'mail-source
258   :type 'directory)
259
260 (defcustom mail-source-default-file-modes 384
261   "Set the mode bits of all new mail files to this integer."
262   :group 'mail-source
263   :type 'integer)
264
265 (defcustom mail-source-delete-incoming nil
266   "*If non-nil, delete incoming files after handling."
267   :group 'mail-source
268   :type 'boolean)
269
270 (defcustom mail-source-incoming-file-prefix "Incoming"
271   "Prefix for file name for storing incoming mail"
272   :group 'mail-source
273   :type 'string)
274
275 (defcustom mail-source-report-new-mail-interval 5
276   "Interval in minutes between checks for new mail."
277   :group 'mail-source
278   :type 'number)
279
280 (defcustom mail-source-idle-time-delay 5
281   "Number of idle seconds to wait before checking for new mail."
282   :group 'mail-source
283   :type 'number)
284
285 (defcustom mail-source-movemail-program nil
286   "If non-nil, name of program for fetching new mail."
287   :group 'mail-source
288   :type '(choice (const nil) string))
289
290 ;;; Internal variables.
291
292 (defvar mail-source-string ""
293   "A dynamically bound string that says what the current mail source is.")
294
295 (defvar mail-source-new-mail-available nil
296   "Flag indicating when new mail is available.")
297
298 (eval-and-compile
299   (defvar mail-source-common-keyword-map
300     '((:plugged))
301     "Mapping from keywords to default values.
302 Common keywords should be listed here.")
303
304   (defvar mail-source-keyword-map
305     '((file
306        (:prescript)
307        (:prescript-delay)
308        (:postscript)
309        (:path (or (getenv "MAIL")
310                   (expand-file-name (user-login-name) rmail-spool-directory))))
311       (directory
312        (:prescript)
313        (:prescript-delay)
314        (:postscript)
315        (:path)
316        (:suffix ".spool")
317        (:predicate identity))
318       (pop
319        (:prescript)
320        (:prescript-delay)
321        (:postscript)
322        (:server (getenv "MAILHOST"))
323        (:port 110)
324        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
325        (:program)
326        (:function)
327        (:password)
328        (:connection)
329        (:authentication password)
330        (:leave))
331       (maildir
332        (:path (or (getenv "MAILDIR") "~/Maildir/"))
333        (:subdirs ("new" "cur"))
334        (:function))
335       (imap
336        (:server (getenv "MAILHOST"))
337        (:port)
338        (:stream)
339        (:program)
340        (:authentication)
341        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
342        (:password)
343        (:mailbox "INBOX")
344        (:predicate "UNSEEN UNDELETED")
345        (:fetchflag "\\Deleted")
346        (:dontexpunge))
347       (webmail
348        (:subtype hotmail)
349        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
350        (:password)
351        (:dontexpunge)
352        (:authentication password)))
353     "Mapping from keywords to default values.
354 All keywords that can be used must be listed here."))
355
356 (defvar mail-source-fetcher-alist
357   '((file mail-source-fetch-file)
358     (directory mail-source-fetch-directory)
359     (pop mail-source-fetch-pop)
360     (maildir mail-source-fetch-maildir)
361     (imap mail-source-fetch-imap)
362     (webmail mail-source-fetch-webmail))
363   "A mapping from source type to fetcher function.")
364
365 (defvar mail-source-password-cache nil)
366
367 (defvar mail-source-plugged t)
368
369 ;;; Functions
370
371 (eval-and-compile
372   (defun mail-source-strip-keyword (keyword)
373     "Strip the leading colon off the KEYWORD."
374     (intern (substring (symbol-name keyword) 1))))
375
376 (eval-and-compile
377   (defun mail-source-bind-1 (type)
378     (let* ((defaults (cdr (assq type mail-source-keyword-map)))
379            default bind)
380       (while (setq default (pop defaults))
381         (push (list (mail-source-strip-keyword (car default))
382                     nil)
383               bind))
384       bind)))
385
386 (defmacro mail-source-bind (type-source &rest body)
387   "Return a `let' form that binds all variables in source TYPE.
388 TYPE-SOURCE is a list where the first element is the TYPE, and
389 the second variable is the SOURCE.
390 At run time, the mail source specifier SOURCE will be inspected,
391 and the variables will be set according to it.  Variables not
392 specified will be given default values.
393
394 After this is done, BODY will be executed in the scope
395 of the `let' form.
396
397 The variables bound and their default values are described by
398 the `mail-source-keyword-map' variable."
399   `(let ,(mail-source-bind-1 (car type-source))
400      (mail-source-set-1 ,(cadr type-source))
401      ,@body))
402
403 (put 'mail-source-bind 'lisp-indent-function 1)
404 (put 'mail-source-bind 'edebug-form-spec '(sexp body))
405
406 (defun mail-source-set-1 (source)
407   (let* ((type (pop source))
408          (defaults (cdr (assq type mail-source-keyword-map)))
409          default value keyword)
410     (while (setq default (pop defaults))
411       (set (mail-source-strip-keyword (setq keyword (car default)))
412            (if (setq value (plist-get source keyword))
413                (mail-source-value value)
414              (mail-source-value (cadr default)))))))
415
416 (eval-and-compile
417   (defun mail-source-bind-common-1 ()
418     (let* ((defaults mail-source-common-keyword-map)
419            default bind)
420       (while (setq default (pop defaults))
421         (push (list (mail-source-strip-keyword (car default))
422                     nil)
423               bind))
424       bind)))
425
426 (defun mail-source-set-common-1 (source)
427   (let* ((type (pop source))
428          (defaults mail-source-common-keyword-map)
429          (defaults-1 (cdr (assq type mail-source-keyword-map)))
430          default value keyword)
431     (while (setq default (pop defaults))
432       (set (mail-source-strip-keyword (setq keyword (car default)))
433            (if (setq value (plist-get source keyword))
434                (mail-source-value value)
435              (if (setq value (assq  keyword defaults-1))
436                  (mail-source-value (cadr value))
437                (mail-source-value (cadr default))))))))
438
439 (defmacro mail-source-bind-common (source &rest body)
440   "Return a `let' form that binds all common variables.
441 See `mail-source-bind'."
442   `(let ,(mail-source-bind-common-1)
443      (mail-source-set-common-1 source)
444      ,@body))
445
446 (put 'mail-source-bind-common 'lisp-indent-function 1)
447 (put 'mail-source-bind-common 'edebug-form-spec '(sexp body))
448
449 (defun mail-source-value (value)
450   "Return the value of VALUE."
451   (cond
452    ;; String
453    ((stringp value)
454     value)
455    ;; Function
456    ((and (listp value)
457          (functionp (car value)))
458     (eval value))
459    ;; Just return the value.
460    (t
461     value)))
462
463 (defun mail-source-fetch (source callback)
464   "Fetch mail from SOURCE and call CALLBACK zero or more times.
465 CALLBACK will be called with the name of the file where (some of)
466 the mail from SOURCE is put.
467 Return the number of files that were found."
468   (mail-source-bind-common source
469     (if (or mail-source-plugged plugged)
470         (save-excursion
471           (let ((function (cadr (assq (car source) mail-source-fetcher-alist)))
472                 (found 0))
473             (unless function
474               (error "%S is an invalid mail source specification" source))
475             ;; If there's anything in the crash box, we do it first.
476             (when (file-exists-p mail-source-crash-box)
477               (message "Processing mail from %s..." mail-source-crash-box)
478               (setq found (mail-source-callback
479                            callback mail-source-crash-box)))
480             (+ found
481                (if (or debug-on-quit debug-on-error)
482                    (funcall function source callback)
483                  (condition-case err
484                      (funcall function source callback)
485                    (error
486                     (if (and (not mail-source-ignore-errors)
487                              (yes-or-no-p
488                               (format "Mail source %s error (%s).  Continue? "
489                                       (if (memq ':password source)
490                                           (let ((s (copy-sequence source)))
491                                             (setcar (cdr (memq ':password s)) 
492                                                     "********")
493                                             s)
494                                         source)
495                                       (cadr err))))
496                       (error "Cannot get new mail"))
497                     0)))))))))
498
499 (eval-and-compile
500   (if (fboundp 'make-temp-file)
501       (defalias 'mail-source-make-complex-temp-name 'make-temp-file)
502     (defun mail-source-make-complex-temp-name (prefix)
503       (let ((newname (make-temp-name prefix))
504             (newprefix prefix))
505         (while (file-exists-p newname)
506           (setq newprefix (concat newprefix "x"))
507           (setq newname (make-temp-name newprefix)))
508         newname))))
509
510 (defun mail-source-callback (callback info)
511   "Call CALLBACK on the mail file, and then remove the mail file.
512 Pass INFO on to CALLBACK."
513   (if (or (not (file-exists-p mail-source-crash-box))
514           (zerop (nth 7 (file-attributes mail-source-crash-box))))
515       (progn
516         (when (file-exists-p mail-source-crash-box)
517           (delete-file mail-source-crash-box))
518         0)
519     (prog1
520         (funcall callback mail-source-crash-box info)
521       (when (file-exists-p mail-source-crash-box)
522         ;; Delete or move the incoming mail out of the way.
523         (if mail-source-delete-incoming
524             (delete-file mail-source-crash-box)
525           (let ((incoming
526                  (mail-source-make-complex-temp-name
527                   (expand-file-name
528                    mail-source-incoming-file-prefix
529                    mail-source-directory))))
530             (unless (file-exists-p (file-name-directory incoming))
531               (make-directory (file-name-directory incoming) t))
532             (rename-file mail-source-crash-box incoming t)))))))
533
534 (defun mail-source-movemail (from to)
535   "Move FROM to TO using movemail."
536   (if (not (file-writable-p to))
537       (error "Can't write to crash box %s.  Not moving mail" to)
538     (let ((to (file-truename (expand-file-name to)))
539           errors result)
540       (setq to (file-truename to)
541             from (file-truename from))
542       ;; Set TO if have not already done so, and rename or copy
543       ;; the file FROM to TO if and as appropriate.
544       (cond
545        ((file-exists-p to)
546         ;; The crash box exists already.
547         t)
548        ((not (file-exists-p from))
549         ;; There is no inbox.
550         (setq to nil))
551        ((zerop (nth 7 (file-attributes from)))
552         ;; Empty file.
553         (setq to nil))
554        (t
555         ;; If getting from mail spool directory, use movemail to move
556         ;; rather than just renaming, so as to interlock with the
557         ;; mailer.
558         (unwind-protect
559             (save-excursion
560               (setq errors (generate-new-buffer " *mail source loss*"))
561               (let ((default-directory "/"))
562                 (setq result
563                       (apply
564                        'call-process
565                        (append
566                         (list
567                          (or mail-source-movemail-program
568                              (expand-file-name "movemail" exec-directory))
569                          nil errors nil from to)))))
570               (when (file-exists-p to)
571                 (set-file-modes to mail-source-default-file-modes))
572               (if (and (or (not (buffer-modified-p errors))
573                            (zerop (buffer-size errors)))
574                        (zerop result))
575                   ;; No output => movemail won.
576                   t
577                 (set-buffer errors)
578                 ;; There may be a warning about older revisions.  We
579                 ;; ignore that.
580                 (goto-char (point-min))
581                 (if (search-forward "older revision" nil t)
582                     t
583                   ;; Probably a real error.
584                   (subst-char-in-region (point-min) (point-max) ?\n ?\  )
585                   (goto-char (point-max))
586                   (skip-chars-backward " \t")
587                   (delete-region (point) (point-max))
588                   (goto-char (point-min))
589                   (when (looking-at "movemail: ")
590                     (delete-region (point-min) (match-end 0)))
591                   ;; Result may be a signal description string.
592                   (unless (yes-or-no-p
593                            (format "movemail: %s (%s return).  Continue? "
594                                    (buffer-string) result))
595                     (error "%s" (buffer-string)))
596                   (setq to nil)))))))
597       (when (and errors
598                  (buffer-name errors))
599         (kill-buffer errors))
600       ;; Return whether we moved successfully or not.
601       to)))
602
603 (defun mail-source-movemail-and-remove (from to)
604   "Move FROM to TO using movemail, then remove FROM if empty."
605   (or (not (mail-source-movemail from to))
606       (not (zerop (nth 7 (file-attributes from))))
607       (delete-file from)))
608
609 (defvar mail-source-read-passwd nil)
610 (defun mail-source-read-passwd (prompt &rest args)
611   "Read a password using PROMPT.
612 If ARGS, PROMPT is used as an argument to `format'."
613   (let ((prompt
614          (if args
615              (apply 'format prompt args)
616            prompt)))
617     (unless mail-source-read-passwd
618       (if (or (fboundp 'read-passwd) (load "passwd" t))
619           (setq mail-source-read-passwd 'read-passwd)
620         (unless (fboundp 'ange-ftp-read-passwd)
621           (autoload 'ange-ftp-read-passwd "ange-ftp"))
622         (setq mail-source-read-passwd 'ange-ftp-read-passwd)))
623     (funcall mail-source-read-passwd prompt)))
624
625 (defun mail-source-fetch-with-program (program)
626   (zerop (call-process shell-file-name nil nil nil
627                        shell-command-switch program)))
628
629 (defun mail-source-run-script (script spec &optional delay)
630   (when script
631     (if (functionp script)
632         (funcall script)
633       (mail-source-call-script
634        (format-spec script spec))))
635   (when delay
636     (sleep-for delay)))
637
638 (defun mail-source-call-script (script)
639   (let ((background nil))
640     (when (string-match "& *$" script)
641       (setq script (substring script 0 (match-beginning 0))
642             background 0))
643     (call-process shell-file-name nil background nil
644                   shell-command-switch script)))
645
646 ;;;
647 ;;; Different fetchers
648 ;;;
649
650 (defun mail-source-fetch-file (source callback)
651   "Fetcher for single-file sources."
652   (mail-source-bind (file source)
653     (mail-source-run-script
654      prescript (format-spec-make ?t mail-source-crash-box)
655      prescript-delay)
656     (let ((mail-source-string (format "file:%s" path)))
657       (if (mail-source-movemail path mail-source-crash-box)
658           (prog1
659               (mail-source-callback callback path)
660             (mail-source-run-script
661              postscript (format-spec-make ?t mail-source-crash-box)))
662         0))))
663
664 (defun mail-source-fetch-directory (source callback)
665   "Fetcher for directory sources."
666   (mail-source-bind (directory source)
667     (mail-source-run-script
668      prescript (format-spec-make ?t path)
669      prescript-delay)
670     (let ((found 0)
671           (mail-source-string (format "directory:%s" path)))
672       (dolist (file (directory-files
673                      path t (concat (regexp-quote suffix) "$")))
674         (when (and (file-regular-p file)
675                    (funcall predicate file)
676                    (mail-source-movemail file mail-source-crash-box))
677           (incf found (mail-source-callback callback file))))
678       (mail-source-run-script
679        postscript (format-spec-make ?t path))
680       found)))
681
682 (defun mail-source-fetch-pop (source callback)
683   "Fetcher for single-file sources."
684   (mail-source-bind (pop source)
685     (mail-source-run-script
686      prescript
687      (format-spec-make ?p password ?t mail-source-crash-box
688                        ?s server ?P port ?u user)
689      prescript-delay)
690     (let ((from (format "%s:%s:%s" server user port))
691           (mail-source-string (format "pop:%s@%s" user server))
692           result)
693       (when (eq authentication 'password)
694         (setq password
695               (or password
696                   (cdr (assoc from mail-source-password-cache))
697                   (mail-source-read-passwd
698                    (format "Password for %s at %s: " user server)))))
699       (when server
700         (setenv "MAILHOST" server))
701       (setq result
702             (cond
703              (program
704               (mail-source-fetch-with-program
705                (format-spec
706                 program
707                 (format-spec-make ?p password ?t mail-source-crash-box
708                                   ?s server ?P port ?u user))))
709              (function
710               (funcall function mail-source-crash-box))
711              ;; The default is to use pop3.el.
712              (t
713               (let ((pop3-password password)
714                     (pop3-maildrop user)
715                     (pop3-mailhost server)
716                     (pop3-port port)
717                     (pop3-authentication-scheme
718                      (if (eq authentication 'apop) 'apop 'pass))
719                     (pop3-connection-type connection)
720                     (pop3-leave-mail-on-server
721                      (or leave
722                          (and (boundp 'pop3-leave-mail-on-server)
723                               pop3-leave-mail-on-server))))
724                 (if (or debug-on-quit debug-on-error)
725                     (save-excursion (pop3-movemail mail-source-crash-box))
726                   (condition-case err
727                       (save-excursion (pop3-movemail mail-source-crash-box))
728                     (error
729                      ;; We nix out the password in case the error
730                      ;; was because of a wrong password being given.
731                      (setq mail-source-password-cache
732                            (delq (assoc from mail-source-password-cache)
733                                  mail-source-password-cache))
734                      (signal (car err) (cdr err)))))))))
735       (if result
736           (progn
737             (when (eq authentication 'password)
738               (unless (assoc from mail-source-password-cache)
739                 (push (cons from password) mail-source-password-cache)))
740             (prog1
741                 (mail-source-callback callback server)
742               ;; Update display-time's mail flag, if relevant.
743               (if (equal source mail-source-primary-source)
744                   (setq mail-source-new-mail-available nil))
745               (mail-source-run-script
746                postscript
747                (format-spec-make ?p password ?t mail-source-crash-box
748                                  ?s server ?P port ?u user))))
749         ;; We nix out the password in case the error
750         ;; was because of a wrong password being given.
751         (setq mail-source-password-cache
752               (delq (assoc from mail-source-password-cache)
753                     mail-source-password-cache))
754         0))))
755
756 (defun mail-source-check-pop (source)
757   "Check whether there is new mail."
758   (mail-source-bind (pop source)
759     (let ((from (format "%s:%s:%s" server user port))
760           (mail-source-string (format "pop:%s@%s" user server))
761           result)
762       (when (eq authentication 'password)
763         (setq password
764               (or password
765                   (cdr (assoc from mail-source-password-cache))
766                   (mail-source-read-passwd
767                    (format "Password for %s at %s: " user server))))
768         (unless (assoc from mail-source-password-cache)
769           (push (cons from password) mail-source-password-cache)))
770       (when server
771         (setenv "MAILHOST" server))
772       (setq result
773             (cond
774              ;; No easy way to check whether mail is waiting for these.
775              (program)
776              (function)
777              ;; The default is to use pop3.el.
778              (t
779               (let ((pop3-password password)
780                     (pop3-maildrop user)
781                     (pop3-mailhost server)
782                     (pop3-port port)
783                     (pop3-authentication-scheme
784                      (if (eq authentication 'apop) 'apop 'pass)))
785                 (if (or debug-on-quit debug-on-error)
786                     (save-excursion (pop3-get-message-count))
787                   (condition-case err
788                       (save-excursion (pop3-get-message-count))
789                     (error
790                      ;; We nix out the password in case the error
791                      ;; was because of a wrong password being given.
792                      (setq mail-source-password-cache
793                            (delq (assoc from mail-source-password-cache)
794                                  mail-source-password-cache))
795                      (signal (car err) (cdr err)))))))))
796       (if result
797           ;; Inform display-time that we have new mail.
798           (setq mail-source-new-mail-available (> result 0))
799         ;; We nix out the password in case the error
800         ;; was because of a wrong password being given.
801         (setq mail-source-password-cache
802               (delq (assoc from mail-source-password-cache)
803                     mail-source-password-cache)))
804       result)))
805
806 (defun mail-source-touch-pop ()
807   "Open and close a POP connection shortly.
808 POP server should be defined in `mail-source-primary-source' (which is
809 preferred) or `mail-sources'.  You may use it for the POP-before-SMTP
810 authentication.  To do that, you need to set the option
811 `message-send-mail-function' to `message-send-mail-with-smtp' or
812 `message-smtpmail-send-it' and put the following line in .gnus file:
813
814 \(add-hook 'message-send-mail-hook 'mail-source-touch-pop)
815 "
816   (let ((sources (if mail-source-primary-source
817                      (list mail-source-primary-source)
818                    mail-sources)))
819     (while sources
820       (if (eq 'pop (car (car sources)))
821           (mail-source-check-pop (car sources)))
822       (setq sources (cdr sources)))))
823
824 (defun mail-source-new-mail-p ()
825   "Handler for `display-time' to indicate when new mail is available."
826   ;; Flash (ie. ring the visible bell) if mail is available.
827   (if (and mail-source-flash mail-source-new-mail-available)
828       (let ((visible-bell t))
829         (ding)))
830   ;; Only report flag setting; flag is updated on a different schedule.
831   mail-source-new-mail-available)
832
833
834 (defvar mail-source-report-new-mail nil)
835 (defvar mail-source-report-new-mail-timer nil)
836 (defvar mail-source-report-new-mail-idle-timer nil)
837
838 (eval-when-compile
839   (if (featurep 'xemacs)
840       (require 'itimer)
841     (require 'timer)))
842
843 (defun mail-source-start-idle-timer ()
844   ;; Start our idle timer if necessary, so we delay the check until the
845   ;; user isn't typing.
846   (unless mail-source-report-new-mail-idle-timer
847     (setq mail-source-report-new-mail-idle-timer
848           (run-with-idle-timer
849            mail-source-idle-time-delay
850            nil
851            (lambda ()
852              (unwind-protect
853                  (mail-source-check-pop mail-source-primary-source)
854                (setq mail-source-report-new-mail-idle-timer nil)))))
855     ;; Since idle timers created when Emacs is already in the idle
856     ;; state don't get activated until Emacs _next_ becomes idle, we
857     ;; need to force our timer to be considered active now.  We do
858     ;; this by being naughty and poking the timer internals directly
859     ;; (element 0 of the vector is nil if the timer is active).
860     (aset mail-source-report-new-mail-idle-timer 0 nil)))
861
862 (defun mail-source-report-new-mail (arg)
863   "Toggle whether to report when new mail is available.
864 This only works when `display-time' is enabled."
865   (interactive "P")
866   (if (not mail-source-primary-source)
867       (error "Need to set `mail-source-primary-source' to check for new mail"))
868   (let ((on (if (null arg)
869                 (not mail-source-report-new-mail)
870               (> (prefix-numeric-value arg) 0))))
871     (setq mail-source-report-new-mail on)
872     (and mail-source-report-new-mail-timer
873          (nnheader-cancel-timer mail-source-report-new-mail-timer))
874     (and mail-source-report-new-mail-idle-timer
875          (nnheader-cancel-timer mail-source-report-new-mail-idle-timer))
876     (setq mail-source-report-new-mail-timer nil)
877     (setq mail-source-report-new-mail-idle-timer nil)
878     (if on
879         (progn
880           (require 'time)
881           ;; display-time-mail-function is an Emacs 21 feature.
882           (setq display-time-mail-function #'mail-source-new-mail-p)
883           ;; Set up the main timer.
884           (setq mail-source-report-new-mail-timer
885                 (nnheader-run-at-time
886                  (* 60 mail-source-report-new-mail-interval)
887                  (* 60 mail-source-report-new-mail-interval)
888                  #'mail-source-start-idle-timer))
889           ;; When you get new mail, clear "Mail" from the mode line.
890           (add-hook 'nnmail-post-get-new-mail-hook
891                     'display-time-event-handler)
892           (message "Mail check enabled"))
893       (setq display-time-mail-function nil)
894       (remove-hook 'nnmail-post-get-new-mail-hook
895                    'display-time-event-handler)
896       (message "Mail check disabled"))))
897
898 (defun mail-source-fetch-maildir (source callback)
899   "Fetcher for maildir sources."
900   (mail-source-bind (maildir source)
901     (let ((found 0)
902           mail-source-string)
903       (unless (string-match "/$" path)
904         (setq path (concat path "/")))
905       (dolist (subdir subdirs)
906         (when (file-directory-p (concat path subdir))
907           (setq mail-source-string (format "maildir:%s%s" path subdir))
908           (dolist (file (directory-files (concat path subdir) t))
909             (when (and (not (file-directory-p file))
910                        (not (if function
911                                 (funcall function file mail-source-crash-box)
912                               (let ((coding-system-for-write
913                                      nnheader-text-coding-system)
914                                     (coding-system-for-read
915                                      nnheader-text-coding-system)
916                                     (output-coding-system
917                                      nnheader-text-coding-system)
918                                     (input-coding-system
919                                      nnheader-text-coding-system))
920                                 (with-temp-file mail-source-crash-box
921                                   (insert-file-contents file)
922                                   (goto-char (point-min))
923 ;;;                               ;; Unix mail format
924 ;;;                               (unless (looking-at "\n*From ")
925 ;;;                                 (insert "From maildir "
926 ;;;                                         (current-time-string) "\n"))
927 ;;;                               (while (re-search-forward "^From " nil t)
928 ;;;                                 (replace-match ">From "))
929 ;;;                               (goto-char (point-max))
930 ;;;                               (insert "\n\n")
931                                   ;; MMDF mail format
932                                   (insert "\001\001\001\001\n"))
933                                 (delete-file file)))))
934               (incf found (mail-source-callback callback file))))))
935       found)))
936
937 (eval-and-compile
938   (autoload 'imap-open "imap")
939   (autoload 'imap-authenticate "imap")
940   (autoload 'imap-mailbox-select "imap")
941   (autoload 'imap-mailbox-unselect "imap")
942   (autoload 'imap-mailbox-close "imap")
943   (autoload 'imap-search "imap")
944   (autoload 'imap-fetch "imap")
945   (autoload 'imap-close "imap")
946   (autoload 'imap-error-text "imap")
947   (autoload 'imap-message-flags-add "imap")
948   (autoload 'imap-list-to-message-set "imap")
949   (autoload 'imap-range-to-message-set "imap"))
950
951 (defvar mail-source-imap-file-coding-system 'binary
952   "Coding system for the crashbox made by `mail-source-fetch-imap'.")
953
954 (defun mail-source-fetch-imap (source callback)
955   "Fetcher for imap sources."
956   (mail-source-bind (imap source)
957     (let ((from (format "%s:%s:%s" server user port))
958           (found 0)
959           (buf (get-buffer-create
960                 (format " *imap source %s:%s:%s *" server user mailbox)))
961           (mail-source-string (format "imap:%s:%s" server mailbox))
962           (imap-shell-program (or (list program) imap-shell-program))
963           remove)
964       (if (and (imap-open server port stream authentication buf)
965                (imap-authenticate
966                 user (or (cdr (assoc from mail-source-password-cache))
967                          password) buf)
968                (imap-mailbox-select mailbox nil buf))
969           (let ((coding-system-for-write mail-source-imap-file-coding-system)
970                 (output-coding-system mail-source-imap-file-coding-system)
971                 str)
972             (with-temp-file mail-source-crash-box
973               ;; Avoid converting 8-bit chars from inserted strings to
974               ;; multibyte.
975               (set-buffer-multibyte nil)
976               ;; remember password
977               (with-current-buffer buf
978                 (when (or imap-password
979                           (assoc from mail-source-password-cache))
980                   (push (cons from imap-password) mail-source-password-cache)))
981               ;; if predicate is nil, use all uids
982               (dolist (uid (imap-search (or predicate "1:*") buf))
983                 (when (setq str
984                             (if (imap-capability 'IMAP4rev1 buf)
985                                 (caddar (imap-fetch uid "BODY.PEEK[]"
986                                                     'BODYDETAIL nil buf))
987                               (imap-fetch uid "RFC822.PEEK" 'RFC822 nil buf)))
988                   (push uid remove)
989                   (insert "From imap " (current-time-string) "\n")
990                   (save-excursion
991                     (insert str "\n\n"))
992                   (while (re-search-forward "^From " nil t)
993                     (replace-match ">From "))
994                   (goto-char (point-max))))
995               (nnheader-ms-strip-cr))
996             (incf found (mail-source-callback callback server))
997             (when (and remove fetchflag)
998               (imap-message-flags-add
999                (imap-range-to-message-set (gnus-compress-sequence remove))
1000                fetchflag nil buf))
1001             (if dontexpunge
1002                 (imap-mailbox-unselect buf)
1003               (imap-mailbox-close nil buf))
1004             (imap-close buf))
1005         (imap-close buf)
1006         ;; We nix out the password in case the error
1007         ;; was because of a wrong password being given.
1008         (setq mail-source-password-cache
1009               (delq (assoc from mail-source-password-cache)
1010                     mail-source-password-cache))
1011         (error (imap-error-text buf)))
1012       (kill-buffer buf)
1013       found)))
1014
1015 (eval-and-compile
1016   (autoload 'webmail-fetch "webmail"))
1017
1018 (defun mail-source-fetch-webmail (source callback)
1019   "Fetch for webmail source."
1020   (mail-source-bind (webmail source)
1021     (let ((mail-source-string (format "webmail:%s:%s" subtype user))
1022           (webmail-newmail-only dontexpunge)
1023           (webmail-move-to-trash-can (not dontexpunge)))
1024       (when (eq authentication 'password)
1025         (setq password
1026               (or password
1027                   (cdr (assoc (format "webmail:%s:%s" subtype user)
1028                               mail-source-password-cache))
1029                   (mail-source-read-passwd
1030                    (format "Password for %s at %s: " user subtype))))
1031         (when (and password
1032                    (not (assoc (format "webmail:%s:%s" subtype user)
1033                                mail-source-password-cache)))
1034           (push (cons (format "webmail:%s:%s" subtype user) password)
1035                 mail-source-password-cache)))
1036       (webmail-fetch mail-source-crash-box subtype user password)
1037       (mail-source-callback callback (symbol-name subtype)))))
1038
1039 (provide 'mail-source)
1040
1041 ;;; mail-source.el ends here