* lisp/gnus-vers.el (gnus-revision-number): Increment to 01.
[elisp/gnus.git-] / lisp / mail-source.el
1 ;;; mail-source.el --- functions for fetching mail
2 ;; Copyright (C) 1999, 2000 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
40 (defgroup mail-source nil
41   "The mail-fetching library."
42   :version "21.1"
43   :group 'gnus)
44
45 ;; Define these at compile time to avoid dragging in imap always.
46 (defconst mail-source-imap-authenticators
47   (eval-when-compile
48     (mapcar (lambda (a)
49               (list 'const (car a)))
50      imap-authenticator-alist)))
51 (defconst mail-source-imap-streams
52   (eval-when-compile
53     (mapcar (lambda (a)
54               (list 'const (car a)))
55      imap-stream-alist)))
56
57 (defcustom mail-sources nil
58   "*Where the mail backends will look for incoming mail.
59 This variable is a list of mail source specifiers.
60 See Info node `(gnus)Mail Source Specifiers'."
61   :group 'mail-source
62   :type `(repeat
63           (choice :format "%[Value Menu%] %v"
64                   :value (file)
65                   (cons :tag "Spool file"
66                         (const :format "" file)
67                         (checklist :tag "Options" :greedy t
68                                    (group :inline t
69                                           (const :format "" :value :path)
70                                           file)))
71                   (cons :tag "Several files in a directory"
72                         (const :format "" directory)
73                         (checklist :tag "Options" :greedy t
74                                    (group :inline t
75                                           (const :format "" :value :path)
76                                           (directory :tag "Path"))
77                                    (group :inline t
78                                           (const :format "" :value :suffix)
79                                           (string :tag "Suffix"))
80                                    (group :inline t
81                                           (const :format "" :value :predicate)
82                                           (function :tag "Predicate"))
83                                    (group :inline t
84                                           (const :format "" :value :prescript)
85                                           (string :tag "Prescript"))
86                                    (group :inline t
87                                           (const :format "" :value :postscript)
88                                           (string :tag "Postscript"))
89                                    (group :inline t
90                                           (const :format "" :value :plugged)
91                                           (boolean :tag "Plugged"))))
92                   (cons :tag "POP3 server"
93                         (const :format "" pop)
94                         (checklist :tag "Options" :greedy t
95                                    (group :inline t
96                                           (const :format "" :value :server) 
97                                           (string :tag "Server"))
98                                    (group :inline t
99                                           (const :format "" :value :port) 
100                                           (choice :tag "Port"
101                                                   :value "pop3" 
102                                                   (number :format "%v")
103                                                   (string :format "%v")))
104                                    (group :inline t
105                                           (const :format "" :value :user)
106                                           (string :tag "User"))
107                                    (group :inline t
108                                           (const :format "" :value :password)
109                                           (string :tag "Password"))
110                                    (group :inline t
111                                           (const :format "" :value :program)
112                                           (string :tag "Program"))
113                                    (group :inline t
114                                           (const :format "" :value :prescript)
115                                           (string :tag "Prescript"))
116                                    (group :inline t
117                                           (const :format "" :value :postscript)
118                                           (string :tag "Postscript"))
119                                    (group :inline t
120                                           (const :format "" :value :function)
121                                           (function :tag "Function"))
122                                    (group :inline t
123                                           (const :format "" 
124                                                  :value :authentication)
125                                           (choice :tag "Authentication"
126                                                   :value apop
127                                                   (const password)
128                                                   (const apop)))
129                                    (group :inline t
130                                           (const :format "" :value :plugged)
131                                           (boolean :tag "Plugged"))))
132                   (cons :tag "Maildir (qmail, postfix...)"
133                         (const :format "" maildir)
134                         (checklist :tag "Options" :greedy t
135                                    (group :inline t
136                                           (const :format "" :value :path)
137                                           (directory :tag "Path"))
138                                    (group :inline t
139                                           (const :format "" :value :plugged)
140                                           (boolean :tag "Plugged"))))
141                   (cons :tag "IMAP server"
142                         (const :format "" imap)
143                         (checklist :tag "Options" :greedy t
144                                    (group :inline t
145                                           (const :format "" :value :server)
146                                           (string :tag "Server"))
147                                    (group :inline t
148                                           (const :format "" :value :port)
149                                           (choice :tag "Port" 
150                                                   :value 143 
151                                                   number string))
152                                    (group :inline t
153                                           (const :format "" :value :user)
154                                           (string :tag "User"))
155                                    (group :inline t
156                                           (const :format "" :value :password)
157                                           (string :tag "Password"))
158                                    (group :inline t
159                                           (const :format "" :value :stream)
160                                           (choice :tag "Stream"
161                                                   :value network
162                                                   ,@mail-source-imap-streams))
163                                    (group :inline t
164                                           (const :format ""
165                                                  :value :authenticator)
166                                           (choice :tag "Authenticator"
167                                                   :value login
168                                                   ,@mail-source-imap-authenticators))
169                                    (group :inline t
170                                           (const :format "" :value :mailbox)
171                                           (string :tag "Mailbox"
172                                                   :value "INBOX"))
173                                    (group :inline t
174                                           (const :format "" :value :predicate)
175                                           (string :tag "Predicate" 
176                                                   :value "UNSEEN UNDELETED"))
177                                    (group :inline t
178                                           (const :format "" :value :fetchflag)
179                                           (string :tag "Fetchflag"
180                                                   :value  "\\Deleted"))
181                                    (group :inline t
182                                           (const :format ""
183                                                  :value :dontexpunge)
184                                           (boolean :tag "Dontexpunge"))
185                                    (group :inline t
186                                           (const :format "" :value :plugged)
187                                           (boolean :tag "Plugged"))))
188                   (cons :tag "Webmail server"
189                         (const :format "" webmail)
190                         (checklist :tag "Options" :greedy t
191                                    (group :inline t 
192                                          (const :format "" :value :subtype)
193                                          ;; Should be generated from
194                                          ;; `webmail-type-definition', but we
195                                          ;; can't require webmail without W3.
196                                          (choice :tag "Subtype"
197                                                  :value hotmail
198                                                  (const hotmail)
199                                                  (const yahoo)
200                                                  (const netaddress)
201                                                  (const netscape)
202                                                  (const my-deja)))
203                                    (group :inline t
204                                           (const :format "" :value :user)
205                                           (string :tag "User"))
206                                    (group :inline t
207                                           (const :format "" :value :password)
208                                           (string :tag "Password"))
209                                    (group :inline t
210                                           (const :format ""
211                                                  :value :dontexpunge)
212                                           (boolean :tag "Dontexpunge"))
213                                    (group :inline t
214                                           (const :format "" :value :plugged)
215                                           (boolean :tag "Plugged")))))))
216
217 (defcustom mail-source-primary-source nil
218   "*Primary source for incoming mail.
219 If non-nil, this maildrop will be checked periodically for new mail."
220   :group 'mail-source
221   :type 'sexp)
222
223 (defcustom mail-source-crash-box "~/.emacs-mail-crash-box"
224   "File where mail will be stored while processing it."
225   :group 'mail-source
226   :type 'file)
227
228 (defcustom mail-source-directory "~/Mail/"
229   "Directory where files (if any) will be stored."
230   :group 'mail-source
231   :type 'directory)
232
233 (defcustom mail-source-default-file-modes 384
234   "Set the mode bits of all new mail files to this integer."
235   :group 'mail-source
236   :type 'integer)
237
238 (defcustom mail-source-delete-incoming nil
239   "*If non-nil, delete incoming files after handling."
240   :group 'mail-source
241   :type 'boolean)
242
243 (defcustom mail-source-incoming-file-prefix "Incoming"
244   "Prefix for file name for storing incoming mail"
245   :group 'mail-source
246   :type 'string)
247
248 (defcustom mail-source-report-new-mail-interval 5
249   "Interval in minutes between checks for new mail."
250   :group 'mail-source
251   :type 'number)
252
253 (defcustom mail-source-idle-time-delay 5
254   "Number of idle seconds to wait before checking for new mail."
255   :group 'mail-source
256   :type 'number)
257
258 ;;; Internal variables.
259
260 (defvar mail-source-string ""
261   "A dynamically bound string that says what the current mail source is.")
262
263 (defvar mail-source-new-mail-available nil
264   "Flag indicating when new mail is available.")
265
266 (eval-and-compile
267   (defvar mail-source-common-keyword-map
268     '((:plugged))
269     "Mapping from keywords to default values.
270 Common keywords should be listed here.")
271
272   (defvar mail-source-keyword-map
273     '((file
274        (:prescript)
275        (:prescript-delay)
276        (:postscript)
277        (:path (or (getenv "MAIL")
278                   (expand-file-name (user-login-name) rmail-spool-directory))))
279       (directory
280        (:path)
281        (:suffix ".spool")
282        (:predicate identity))
283       (pop
284        (:prescript)
285        (:prescript-delay)
286        (:postscript)
287        (:server (getenv "MAILHOST"))
288        (:port 110)
289        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
290        (:program)
291        (:function)
292        (:password)
293        (:connection)
294        (:authentication password)
295        (:leave))
296       (maildir
297        (:path (or (getenv "MAILDIR") "~/Maildir/"))
298        (:subdirs ("new" "cur"))
299        (:function))
300       (imap
301        (:server (getenv "MAILHOST"))
302        (:port)
303        (:stream)
304        (:authentication)
305        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
306        (:password)
307        (:mailbox "INBOX")
308        (:predicate "UNSEEN UNDELETED")
309        (:fetchflag "\\Deleted")
310        (:dontexpunge))
311       (webmail
312        (:subtype hotmail)
313        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
314        (:password)
315        (:dontexpunge)
316        (:authentication password)))
317     "Mapping from keywords to default values.
318 All keywords that can be used must be listed here."))
319
320 (defvar mail-source-fetcher-alist
321   '((file mail-source-fetch-file)
322     (directory mail-source-fetch-directory)
323     (pop mail-source-fetch-pop)
324     (maildir mail-source-fetch-maildir)
325     (imap mail-source-fetch-imap)
326     (webmail mail-source-fetch-webmail))
327   "A mapping from source type to fetcher function.")
328
329 (defvar mail-source-password-cache nil)
330
331 (defvar mail-source-plugged t)
332
333 ;;; Functions
334
335 (eval-and-compile
336   (defun mail-source-strip-keyword (keyword)
337     "Strip the leading colon off the KEYWORD."
338     (intern (substring (symbol-name keyword) 1))))
339
340 (eval-and-compile
341   (defun mail-source-bind-1 (type)
342     (let* ((defaults (cdr (assq type mail-source-keyword-map)))
343            default bind)
344       (while (setq default (pop defaults))
345         (push (list (mail-source-strip-keyword (car default))
346                     nil)
347               bind))
348       bind)))
349
350 (defmacro mail-source-bind (type-source &rest body)
351   "Return a `let' form that binds all variables in source TYPE.
352 TYPE-SOURCE is a list where the first element is the TYPE, and
353 the second variable is the SOURCE.
354 At run time, the mail source specifier SOURCE will be inspected,
355 and the variables will be set according to it.  Variables not
356 specified will be given default values.
357
358 After this is done, BODY will be executed in the scope
359 of the `let' form.
360
361 The variables bound and their default values are described by
362 the `mail-source-keyword-map' variable."
363   `(let ,(mail-source-bind-1 (car type-source))
364      (mail-source-set-1 ,(cadr type-source))
365      ,@body))
366
367 (put 'mail-source-bind 'lisp-indent-function 1)
368 (put 'mail-source-bind 'edebug-form-spec '(form body))
369
370 (defun mail-source-set-1 (source)
371   (let* ((type (pop source))
372          (defaults (cdr (assq type mail-source-keyword-map)))
373          default value keyword)
374     (while (setq default (pop defaults))
375       (set (mail-source-strip-keyword (setq keyword (car default)))
376            (if (setq value (plist-get source keyword))
377                (mail-source-value value)
378              (mail-source-value (cadr default)))))))
379
380 (eval-and-compile
381   (defun mail-source-bind-common-1 ()
382     (let* ((defaults mail-source-common-keyword-map)
383            default bind)
384       (while (setq default (pop defaults))
385         (push (list (mail-source-strip-keyword (car default))
386                     nil)
387               bind))
388       bind)))
389
390 (defun mail-source-set-common-1 (source)
391   (let* ((type (pop source))
392          (defaults mail-source-common-keyword-map)
393          (defaults-1 (cdr (assq type mail-source-keyword-map)))
394          default value keyword)
395     (while (setq default (pop defaults))
396       (set (mail-source-strip-keyword (setq keyword (car default)))
397            (if (setq value (plist-get source keyword))
398                (mail-source-value value)
399              (if (setq value (assq  keyword defaults-1))
400                  (mail-source-value (cadr value))
401                (mail-source-value (cadr default))))))))
402
403 (defmacro mail-source-bind-common (source &rest body)
404   "Return a `let' form that binds all common variables.
405 See `mail-source-bind'."
406   `(let ,(mail-source-bind-common-1)
407      (mail-source-set-common-1 source)
408      ,@body))
409
410 (put 'mail-source-bind-common 'lisp-indent-function 1)
411 (put 'mail-source-bind-common 'edebug-form-spec '(form body))
412
413 (defun mail-source-value (value)
414   "Return the value of VALUE."
415   (cond
416    ;; String
417    ((stringp value)
418     value)
419    ;; Function
420    ((and (listp value)
421          (functionp (car value)))
422     (eval value))
423    ;; Just return the value.
424    (t
425     value)))
426
427 (defun mail-source-fetch (source callback)
428   "Fetch mail from SOURCE and call CALLBACK zero or more times.
429 CALLBACK will be called with the name of the file where (some of)
430 the mail from SOURCE is put.
431 Return the number of files that were found."
432   (mail-source-bind-common source
433     (if (or mail-source-plugged plugged)
434         (save-excursion
435           (let ((function (cadr (assq (car source) mail-source-fetcher-alist)))
436                 (found 0))
437             (unless function
438               (error "%S is an invalid mail source specification" source))
439             ;; If there's anything in the crash box, we do it first.
440             (when (file-exists-p mail-source-crash-box)
441               (message "Processing mail from %s..." mail-source-crash-box)
442               (setq found (mail-source-callback
443                            callback mail-source-crash-box)))
444             (+ found
445                (condition-case err
446                    (funcall function source callback)
447                  (error
448                   (unless (yes-or-no-p
449                            (format "Mail source error (%s).  Continue? " err))
450                     (error "Cannot get new mail."))
451                   0))))))))
452
453 (defun mail-source-make-complex-temp-name (prefix)
454   (let ((newname (make-temp-name prefix))
455         (newprefix prefix))
456     (while (file-exists-p newname)
457       (setq newprefix (concat newprefix "x"))
458       (setq newname (make-temp-name newprefix)))
459     newname))
460
461 (defun mail-source-callback (callback info)
462   "Call CALLBACK on the mail file, and then remove the mail file.
463 Pass INFO on to CALLBACK."
464   (if (or (not (file-exists-p mail-source-crash-box))
465           (zerop (nth 7 (file-attributes mail-source-crash-box))))
466       (progn
467         (when (file-exists-p mail-source-crash-box)
468           (delete-file mail-source-crash-box))
469         0)
470     (prog1
471         (funcall callback mail-source-crash-box info)
472       (when (file-exists-p mail-source-crash-box)
473         ;; Delete or move the incoming mail out of the way.
474         (if mail-source-delete-incoming
475             (delete-file mail-source-crash-box)
476           (let ((incoming
477                  (mail-source-make-complex-temp-name
478                   (expand-file-name
479                    mail-source-incoming-file-prefix
480                    mail-source-directory))))
481             (unless (file-exists-p (file-name-directory incoming))
482               (make-directory (file-name-directory incoming) t))
483             (rename-file mail-source-crash-box incoming t)))))))
484
485 (defun mail-source-movemail (from to)
486   "Move FROM to TO using movemail."
487   (if (not (file-writable-p to))
488       (error "Can't write to crash box %s.  Not moving mail" to)
489     (let ((to (file-truename (expand-file-name to)))
490           errors result)
491       (setq to (file-truename to)
492             from (file-truename from))
493       ;; Set TO if have not already done so, and rename or copy
494       ;; the file FROM to TO if and as appropriate.
495       (cond
496        ((file-exists-p to)
497         ;; The crash box exists already.
498         t)
499        ((not (file-exists-p from))
500         ;; There is no inbox.
501         (setq to nil))
502        ((zerop (nth 7 (file-attributes from)))
503         ;; Empty file.
504         (setq to nil))
505        (t
506         ;; If getting from mail spool directory, use movemail to move
507         ;; rather than just renaming, so as to interlock with the
508         ;; mailer.
509         (unwind-protect
510             (save-excursion
511               (setq errors (generate-new-buffer " *mail source loss*"))
512               (let ((default-directory "/"))
513                 (setq result
514                       (apply
515                        'call-process
516                        (append
517                         (list
518                          (expand-file-name "movemail" exec-directory)
519                          nil errors nil from to)))))
520               (when (file-exists-p to)
521                 (set-file-modes to mail-source-default-file-modes))
522               (if (and (not (buffer-modified-p errors))
523                        (zerop result))
524                   ;; No output => movemail won.
525                   t
526                 (set-buffer errors)
527                 ;; There may be a warning about older revisions.  We
528                 ;; ignore that.
529                 (goto-char (point-min))
530                 (if (search-forward "older revision" nil t)
531                     t
532                   ;; Probably a real error.
533                   (subst-char-in-region (point-min) (point-max) ?\n ?\  )
534                   (goto-char (point-max))
535                   (skip-chars-backward " \t")
536                   (delete-region (point) (point-max))
537                   (goto-char (point-min))
538                   (when (looking-at "movemail: ")
539                     (delete-region (point-min) (match-end 0)))
540                   (unless (yes-or-no-p
541                            (format "movemail: %s (%d return).  Continue? "
542                                    (buffer-string) result))
543                     (error "%s" (buffer-string)))
544                   (setq to nil)))))))
545       (when (and errors
546                  (buffer-name errors))
547         (kill-buffer errors))
548       ;; Return whether we moved successfully or not.
549       to)))
550
551 (defun mail-source-movemail-and-remove (from to)
552   "Move FROM to TO using movemail, then remove FROM if empty."
553   (or (not (mail-source-movemail from to))
554       (not (zerop (nth 7 (file-attributes from))))
555       (delete-file from)))
556
557 (defvar mail-source-read-passwd nil)
558 (defun mail-source-read-passwd (prompt &rest args)
559   "Read a password using PROMPT.
560 If ARGS, PROMPT is used as an argument to `format'."
561   (let ((prompt
562          (if args
563              (apply 'format prompt args)
564            prompt)))
565     (unless mail-source-read-passwd
566       (if (or (fboundp 'read-passwd) (load "passwd" t))
567           (setq mail-source-read-passwd 'read-passwd)
568         (unless (fboundp 'ange-ftp-read-passwd)
569           (autoload 'ange-ftp-read-passwd "ange-ftp"))
570         (setq mail-source-read-passwd 'ange-ftp-read-passwd)))
571     (funcall mail-source-read-passwd prompt)))
572
573 (defun mail-source-fetch-with-program (program)
574   (zerop (call-process shell-file-name nil nil nil
575                        shell-command-switch program)))
576
577 (defun mail-source-run-script (script spec &optional delay)
578   (when script
579     (if (and (symbolp script) (fboundp script))
580         (funcall script)
581       (mail-source-call-script
582        (format-spec script spec))))
583   (when delay
584     (sleep-for delay)))
585
586 (defun mail-source-call-script (script)
587   (let ((background nil))
588     (when (string-match "& *$" script)
589       (setq script (substring script 0 (match-beginning 0))
590             background 0))
591     (call-process shell-file-name nil background nil
592                   shell-command-switch script)))
593
594 ;;;
595 ;;; Different fetchers
596 ;;;
597
598 (defun mail-source-fetch-file (source callback)
599   "Fetcher for single-file sources."
600   (mail-source-bind (file source)
601     (mail-source-run-script
602      prescript (format-spec-make ?t mail-source-crash-box)
603      prescript-delay)
604     (let ((mail-source-string (format "file:%s" path)))
605       (if (mail-source-movemail path mail-source-crash-box)
606           (prog1
607               (mail-source-callback callback path)
608             (mail-source-run-script
609              postscript (format-spec-make ?t mail-source-crash-box)))
610         0))))
611
612 (defun mail-source-fetch-directory (source callback)
613   "Fetcher for directory sources."
614   (mail-source-bind (directory source)
615     (let ((found 0)
616           (mail-source-string (format "directory:%s" path)))
617       (dolist (file (directory-files
618                      path t (concat (regexp-quote suffix) "$")))
619         (when (and (file-regular-p file)
620                    (funcall predicate file)
621                    (mail-source-movemail file mail-source-crash-box))
622           (incf found (mail-source-callback callback file))))
623       found)))
624
625 (defun mail-source-fetch-pop (source callback)
626   "Fetcher for single-file sources."
627   (mail-source-bind (pop source)
628     (mail-source-run-script
629      prescript
630      (format-spec-make ?p password ?t mail-source-crash-box
631                        ?s server ?P port ?u user)
632      prescript-delay)
633     (let ((from (format "%s:%s:%s" server user port))
634           (mail-source-string (format "pop:%s@%s" user server))
635           result)
636       (when (eq authentication 'password)
637         (setq password
638               (or password
639                   (cdr (assoc from mail-source-password-cache))
640                   (mail-source-read-passwd
641                    (format "Password for %s at %s: " user server)))))
642       (when server
643         (setenv "MAILHOST" server))
644       (setq result
645             (cond
646              (program
647               (mail-source-fetch-with-program
648                (format-spec
649                 program
650                 (format-spec-make ?p password ?t mail-source-crash-box
651                                   ?s server ?P port ?u user))))
652              (function
653               (funcall function mail-source-crash-box))
654              ;; The default is to use pop3.el.
655              (t
656               (let ((pop3-password password)
657                     (pop3-maildrop user)
658                     (pop3-mailhost server)
659                     (pop3-port port)
660                     (pop3-authentication-scheme
661                      (if (eq authentication 'apop) 'apop 'pass))
662                     (pop3-connection-type connection)
663                     (pop3-leave-mail-on-server
664                      (or leave
665                          (and (boundp 'pop3-leave-mail-on-server)
666                               pop3-leave-mail-on-server))))
667                 (condition-case err
668                     (save-excursion (pop3-movemail mail-source-crash-box))
669                   (error
670                    ;; We nix out the password in case the error
671                    ;; was because of a wrong password being given.
672                    (setq mail-source-password-cache
673                          (delq (assoc from mail-source-password-cache)
674                                mail-source-password-cache))
675                    (signal (car err) (cdr err))))))))
676       (if result
677           (progn
678             (when (eq authentication 'password)
679               (unless (assoc from mail-source-password-cache)
680                 (push (cons from password) mail-source-password-cache)))
681             (prog1
682                 (mail-source-callback callback server)
683               ;; Update display-time's mail flag, if relevant.
684               (if (equal source mail-source-primary-source)
685                   (setq mail-source-new-mail-available nil))
686               (mail-source-run-script
687                postscript
688                (format-spec-make ?p password ?t mail-source-crash-box
689                                  ?s server ?P port ?u user))))
690         ;; We nix out the password in case the error
691         ;; was because of a wrong password being given.
692         (setq mail-source-password-cache
693               (delq (assoc from mail-source-password-cache)
694                     mail-source-password-cache))
695         0))))
696
697 (defun mail-source-check-pop (source)
698   "Check whether there is new mail."
699   (mail-source-bind (pop source)
700     (let ((from (format "%s:%s:%s" server user port))
701           (mail-source-string (format "pop:%s@%s" user server))
702           result)
703       (when (eq authentication 'password)
704         (setq password
705               (or password
706                   (cdr (assoc from mail-source-password-cache))
707                   (mail-source-read-passwd
708                    (format "Password for %s at %s: " user server))))
709         (unless (assoc from mail-source-password-cache)
710           (push (cons from password) mail-source-password-cache)))
711       (when server
712         (setenv "MAILHOST" server))
713       (setq result
714             (cond
715              ;; No easy way to check whether mail is waiting for these.
716              (program)
717              (function)
718              ;; The default is to use pop3.el.
719              (t
720               (let ((pop3-password password)
721                     (pop3-maildrop user)
722                     (pop3-mailhost server)
723                     (pop3-port port)
724                     (pop3-authentication-scheme
725                      (if (eq authentication 'apop) 'apop 'pass)))
726                 (condition-case err
727                     (save-excursion (pop3-get-message-count))
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           ;; Inform display-time that we have new mail.
737           (setq mail-source-new-mail-available (> result 0))
738         ;; We nix out the password in case the error
739         ;; was because of a wrong password being given.
740         (setq mail-source-password-cache
741               (delq (assoc from mail-source-password-cache)
742                     mail-source-password-cache)))
743       result)))
744
745 (defun mail-source-new-mail-p ()
746   "Handler for `display-time' to indicate when new mail is available."
747   ;; Only report flag setting; flag is updated on a different schedule.
748   mail-source-new-mail-available)
749
750
751 (defvar mail-source-report-new-mail nil)
752 (defvar mail-source-report-new-mail-timer nil)
753 (defvar mail-source-report-new-mail-idle-timer nil)
754
755 (eval-when-compile 
756   (if (featurep 'xemacs)
757       (require 'itimer)
758     (require 'timer)))
759
760 (defun mail-source-start-idle-timer ()
761   ;; Start our idle timer if necessary, so we delay the check until the
762   ;; user isn't typing.
763   (unless mail-source-report-new-mail-idle-timer
764     (setq mail-source-report-new-mail-idle-timer
765           (run-with-idle-timer
766            mail-source-idle-time-delay
767            nil
768            (lambda ()
769              (mail-source-check-pop mail-source-primary-source)
770              (setq mail-source-report-new-mail-idle-timer nil))))
771     ;; Since idle timers created when Emacs is already in the idle
772     ;; state don't get activated until Emacs _next_ becomes idle, we
773     ;; need to force our timer to be considered active now.  We do
774     ;; this by being naughty and poking the timer internals directly
775     ;; (element 0 of the vector is nil if the timer is active).
776     (aset mail-source-report-new-mail-idle-timer 0 nil)))
777
778 (defun mail-source-report-new-mail (arg)
779   "Toggle whether to report when new mail is available.
780 This only works when `display-time' is enabled."
781   (interactive "P")
782   (if (not mail-source-primary-source)
783       (error "Need to set `mail-source-primary-source' to check for new mail."))
784   (let ((on (if (null arg)
785                 (not mail-source-report-new-mail)
786               (> (prefix-numeric-value arg) 0))))
787     (setq mail-source-report-new-mail on)
788     (and mail-source-report-new-mail-timer
789          (nnheader-cancel-timer mail-source-report-new-mail-timer))
790     (and mail-source-report-new-mail-idle-timer
791          (nnheader-cancel-timer mail-source-report-new-mail-idle-timer))
792     (setq mail-source-report-new-mail-timer nil)
793     (setq mail-source-report-new-mail-idle-timer nil)
794     (if on
795         (progn
796           (require 'time)
797           ;; display-time-mail-function is an Emacs 21 feature.
798           (setq display-time-mail-function #'mail-source-new-mail-p)
799           ;; Set up the main timer.
800           (setq mail-source-report-new-mail-timer
801                 (nnheader-run-at-time
802                  (* 60 mail-source-report-new-mail-interval)
803                  (* 60 mail-source-report-new-mail-interval)
804                  #'mail-source-start-idle-timer))
805           ;; When you get new mail, clear "Mail" from the mode line.
806           (add-hook 'nnmail-post-get-new-mail-hook
807                     'display-time-event-handler)
808           (message "Mail check enabled"))
809       (setq display-time-mail-function nil)
810       (remove-hook 'nnmail-post-get-new-mail-hook
811                    'display-time-event-handler)
812       (message "Mail check disabled"))))
813
814 (defun mail-source-fetch-maildir (source callback)
815   "Fetcher for maildir sources."
816   (mail-source-bind (maildir source)
817     (let ((found 0)
818           mail-source-string)
819       (unless (string-match "/$" path)
820         (setq path (concat path "/")))
821       (dolist (subdir subdirs)
822         (when (file-directory-p (concat path subdir))
823           (setq mail-source-string (format "maildir:%s%s" path subdir))
824           (dolist (file (directory-files (concat path subdir) t))
825             (when (and (not (file-directory-p file))
826                        (not (if function
827                                 (funcall function file mail-source-crash-box)
828                               (let ((coding-system-for-write
829                                      nnheader-text-coding-system)
830                                     (coding-system-for-read
831                                      nnheader-text-coding-system)
832                                     (output-coding-system
833                                      nnheader-text-coding-system)
834                                     (input-coding-system
835                                      nnheader-text-coding-system))
836                                 (with-temp-file mail-source-crash-box
837                                   (insert-file-contents file)
838                                   (goto-char (point-min))
839 ;;;                               ;; Unix mail format
840 ;;;                               (unless (looking-at "\n*From ")
841 ;;;                                 (insert "From maildir " 
842 ;;;                                         (current-time-string) "\n"))
843 ;;;                               (while (re-search-forward "^From " nil t)
844 ;;;                                 (replace-match ">From "))
845 ;;;                               (goto-char (point-max))
846 ;;;                               (insert "\n\n")
847                                   ;; MMDF mail format
848                                   (insert "\001\001\001\001\n"))
849                                 (delete-file file)))))
850               (incf found (mail-source-callback callback file))))))
851       found)))
852
853 (eval-and-compile
854   (autoload 'imap-open "imap")
855   (autoload 'imap-authenticate "imap")
856   (autoload 'imap-mailbox-select "imap")
857   (autoload 'imap-mailbox-unselect "imap")
858   (autoload 'imap-mailbox-close "imap")
859   (autoload 'imap-search "imap")
860   (autoload 'imap-fetch "imap")
861   (autoload 'imap-close "imap")
862   (autoload 'imap-error-text "imap")
863   (autoload 'imap-message-flags-add "imap")
864   (autoload 'imap-list-to-message-set "imap")
865   (autoload 'imap-range-to-message-set "imap"))
866
867 (defvar mail-source-imap-file-coding-system 'binary
868   "Coding system for the crashbox made by `mail-source-fetch-imap'.")
869
870 (defun mail-source-fetch-imap (source callback)
871   "Fetcher for imap sources."
872   (mail-source-bind (imap source)
873     (let ((from (format "%s:%s:%s" server user port))
874           (found 0)
875           (buf (get-buffer-create
876                 (format " *imap source %s:%s:%s *" server user mailbox)))
877           (mail-source-string (format "imap:%s:%s" server mailbox))
878           remove)
879       (if (and (imap-open server port stream authentication buf)
880                (imap-authenticate
881                 user (or (cdr (assoc from mail-source-password-cache))
882                          password) buf)
883                (imap-mailbox-select mailbox nil buf))
884           (let ((coding-system-for-write mail-source-imap-file-coding-system)
885                 (output-coding-system mail-source-imap-file-coding-system)
886                 str)
887             (with-temp-file mail-source-crash-box
888               ;; Avoid converting 8-bit chars from inserted strings to
889               ;; multibyte.
890               (set-buffer-multibyte nil)
891               ;; remember password
892               (with-current-buffer buf
893                 (when (or imap-password
894                           (assoc from mail-source-password-cache))
895                   (push (cons from imap-password) mail-source-password-cache)))
896               ;; if predicate is nil, use all uids
897               (dolist (uid (imap-search (or predicate "1:*") buf))
898                 (when (setq str (imap-fetch uid "RFC822.PEEK" 'RFC822 nil buf))
899                   (push uid remove)
900                   (insert "From imap " (current-time-string) "\n")
901                   (save-excursion
902                     (insert str "\n\n"))
903                   (while (re-search-forward "^From " nil t)
904                     (replace-match ">From "))
905                   (goto-char (point-max))))
906               (nnheader-ms-strip-cr))
907             (incf found (mail-source-callback callback server))
908             (when (and remove fetchflag)
909               (imap-message-flags-add
910                (imap-range-to-message-set (gnus-compress-sequence remove))
911                fetchflag nil buf))
912             (if dontexpunge
913                 (imap-mailbox-unselect buf)
914               (imap-mailbox-close buf))
915             (imap-close buf))
916         (imap-close buf)
917         ;; We nix out the password in case the error
918         ;; was because of a wrong password being given.
919         (setq mail-source-password-cache
920               (delq (assoc from mail-source-password-cache)
921                     mail-source-password-cache))
922         (error (imap-error-text buf)))
923       (kill-buffer buf)
924       found)))
925
926 (eval-and-compile
927   (autoload 'webmail-fetch "webmail"))
928
929 (defun mail-source-fetch-webmail (source callback)
930   "Fetch for webmail source."
931   (mail-source-bind (webmail source)
932     (let ((mail-source-string (format "webmail:%s:%s" subtype user))
933           (webmail-newmail-only dontexpunge)
934           (webmail-move-to-trash-can (not dontexpunge)))
935       (when (eq authentication 'password)
936         (setq password
937               (or password
938                   (cdr (assoc (format "webmail:%s:%s" subtype user) 
939                               mail-source-password-cache))
940                   (mail-source-read-passwd
941                    (format "Password for %s at %s: " user subtype))))
942         (when (and password
943                    (not (assoc (format "webmail:%s:%s" subtype user) 
944                                mail-source-password-cache)))
945           (push (cons (format "webmail:%s:%s" subtype user) password) 
946                 mail-source-password-cache)))
947       (webmail-fetch mail-source-crash-box subtype user password)
948       (mail-source-callback callback (symbol-name subtype)))))
949
950 (provide 'mail-source)
951
952 ;;; mail-source.el ends here