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