Synch with Gnus.
[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 (require 'cl))
29 (require 'nnheader)
30 (eval-and-compile
31   (autoload 'pop3-movemail "pop3")
32   (autoload 'pop3-get-message-count "pop3"))
33 (require 'format-spec)
34
35 (defgroup mail-source nil
36   "The mail-fetching library."
37   :group 'gnus)
38
39 (defcustom mail-sources nil
40   "*Where the mail backends will look for incoming mail.
41 This variable is a list of mail source specifiers."
42   :group 'mail-source
43   :type 'sexp)
44
45 (defcustom mail-source-primary-source nil
46   "*Primary source for incoming mail.
47 If non-nil, this maildrop will be checked periodically for new mail."
48   :group 'mail-source
49   :type 'sexp)
50
51 (defcustom mail-source-crash-box "~/.emacs-mail-crash-box"
52   "File where mail will be stored while processing it."
53   :group 'mail-source
54   :type 'file)
55
56 (defcustom mail-source-directory "~/Mail/"
57   "Directory where files (if any) will be stored."
58   :group 'mail-source
59   :type 'directory)
60
61 (defcustom mail-source-default-file-modes 384
62   "Set the mode bits of all new mail files to this integer."
63   :group 'mail-source
64   :type 'integer)
65
66 (defcustom mail-source-delete-incoming nil
67   "*If non-nil, delete incoming files after handling."
68   :group 'mail-source
69   :type 'boolean)
70
71 (defcustom mail-source-incoming-file-prefix "Incoming"
72   "Prefix for file name for storing incoming mail"
73   :group 'mail-source
74   :type 'string)
75
76 (defcustom mail-source-report-new-mail-interval 5
77   "Interval in minutes between checks for new mail."
78   :group 'mail-source
79   :type 'number)
80
81 (defcustom mail-source-idle-time-delay 5
82   "Number of idle seconds to wait before checking for new mail."
83   :group 'mail-source
84   :type 'number)
85
86 ;;; Internal variables.
87
88 (defvar mail-source-string ""
89   "A dynamically bound string that says what the current mail source is.")
90
91 (defvar mail-source-new-mail-available nil
92   "Flag indicating when new mail is available.")
93
94 (eval-and-compile
95   (defvar mail-source-common-keyword-map
96     '((:plugged))
97     "Mapping from keywords to default values.
98 Common keywords should be listed here.")
99
100   (defvar mail-source-keyword-map
101     '((file
102        (:prescript)
103        (:prescript-delay)
104        (:postscript)
105        (:path (or (getenv "MAIL")
106                   (concat "/usr/spool/mail/" (user-login-name)))))
107       (directory
108        (:path)
109        (:suffix ".spool")
110        (:predicate identity))
111       (pop
112        (:prescript)
113        (:prescript-delay)
114        (:postscript)
115        (:server (getenv "MAILHOST"))
116        (:port 110)
117        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
118        (:program)
119        (:function)
120        (:password)
121        (:connection)
122        (:authentication password))
123       (maildir
124        (:path (or (getenv "MAILDIR") "~/Maildir/"))
125        (:subdirs ("new" "cur"))
126        (:function))
127       (imap
128        (:server (getenv "MAILHOST"))
129        (:port)
130        (:stream)
131        (:authentication)
132        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
133        (:password)
134        (:mailbox "INBOX")
135        (:predicate "UNSEEN UNDELETED")
136        (:fetchflag "\\Deleted")
137        (:dontexpunge))
138       (webmail
139        (:subtype hotmail)
140        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
141        (:password)
142        (:dontexpunge)
143        (:authentication password)))
144     "Mapping from keywords to default values.
145 All keywords that can be used must be listed here."))
146
147 (defvar mail-source-fetcher-alist
148   '((file mail-source-fetch-file)
149     (directory mail-source-fetch-directory)
150     (pop mail-source-fetch-pop)
151     (maildir mail-source-fetch-maildir)
152     (imap mail-source-fetch-imap)
153     (webmail mail-source-fetch-webmail))
154   "A mapping from source type to fetcher function.")
155
156 (defvar mail-source-password-cache nil)
157
158 (defvar mail-source-plugged t)
159
160 ;;; Functions
161
162 (eval-and-compile
163   (defun mail-source-strip-keyword (keyword)
164     "Strip the leading colon off the KEYWORD."
165     (intern (substring (symbol-name keyword) 1))))
166
167 (eval-and-compile
168   (defun mail-source-bind-1 (type)
169     (let* ((defaults (cdr (assq type mail-source-keyword-map)))
170            default bind)
171       (while (setq default (pop defaults))
172         (push (list (mail-source-strip-keyword (car default))
173                     nil)
174               bind))
175       bind)))
176
177 (defmacro mail-source-bind (type-source &rest body)
178   "Return a `let' form that binds all variables in source TYPE.
179 TYPE-SOURCE is a list where the first element is the TYPE, and
180 the second variable is the SOURCE.
181 At run time, the mail source specifier SOURCE will be inspected,
182 and the variables will be set according to it.  Variables not
183 specified will be given default values.
184
185 After this is done, BODY will be executed in the scope
186 of the `let' form.
187
188 The variables bound and their default values are described by
189 the `mail-source-keyword-map' variable."
190   `(let ,(mail-source-bind-1 (car type-source))
191      (mail-source-set-1 ,(cadr type-source))
192      ,@body))
193
194 (put 'mail-source-bind 'lisp-indent-function 1)
195 (put 'mail-source-bind 'edebug-form-spec '(form body))
196
197 (defun mail-source-set-1 (source)
198   (let* ((type (pop source))
199          (defaults (cdr (assq type mail-source-keyword-map)))
200          default value keyword)
201     (while (setq default (pop defaults))
202       (set (mail-source-strip-keyword (setq keyword (car default)))
203            (if (setq value (plist-get source keyword))
204                (mail-source-value value)
205              (mail-source-value (cadr default)))))))
206
207 (eval-and-compile
208   (defun mail-source-bind-common-1 ()
209     (let* ((defaults mail-source-common-keyword-map)
210            default bind)
211       (while (setq default (pop defaults))
212         (push (list (mail-source-strip-keyword (car default))
213                     nil)
214               bind))
215       bind)))
216
217 (defun mail-source-set-common-1 (source)
218   (let* ((type (pop source))
219          (defaults mail-source-common-keyword-map)
220          (defaults-1 (cdr (assq type mail-source-keyword-map)))
221          default value keyword)
222     (while (setq default (pop defaults))
223       (set (mail-source-strip-keyword (setq keyword (car default)))
224            (if (setq value (plist-get source keyword))
225                (mail-source-value value)
226              (if (setq value (assq  keyword defaults-1))
227                  (mail-source-value (cadr value))
228                (mail-source-value (cadr default))))))))
229
230 (defmacro mail-source-bind-common (source &rest body)
231   "Return a `let' form that binds all common variables.
232 See `mail-source-bind'."
233   `(let ,(mail-source-bind-common-1)
234      (mail-source-set-common-1 source)
235      ,@body))
236
237 (put 'mail-source-bind-common 'lisp-indent-function 1)
238 (put 'mail-source-bind-common 'edebug-form-spec '(form body))
239
240 (defun mail-source-value (value)
241   "Return the value of VALUE."
242   (cond
243    ;; String
244    ((stringp value)
245     value)
246    ;; Function
247    ((and (listp value)
248          (functionp (car value)))
249     (eval value))
250    ;; Just return the value.
251    (t
252     value)))
253
254 (defun mail-source-fetch (source callback)
255   "Fetch mail from SOURCE and call CALLBACK zero or more times.
256 CALLBACK will be called with the name of the file where (some of)
257 the mail from SOURCE is put.
258 Return the number of files that were found."
259   (mail-source-bind-common source
260     (if (or mail-source-plugged plugged)
261         (save-excursion
262           (let ((function (cadr (assq (car source) mail-source-fetcher-alist)))
263                 (found 0))
264             (unless function
265               (error "%S is an invalid mail source specification" source))
266             ;; If there's anything in the crash box, we do it first.
267             (when (file-exists-p mail-source-crash-box)
268               (message "Processing mail from %s..." mail-source-crash-box)
269               (setq found (mail-source-callback
270                            callback mail-source-crash-box)))
271             (+ found
272                (condition-case err
273                    (funcall function source callback)
274                  (error
275                   (unless (yes-or-no-p
276                            (format "Mail source error (%s).  Continue? " err))
277                     (error "Cannot get new mail."))
278                   0))))))))
279
280 (defun mail-source-make-complex-temp-name (prefix)
281   (let ((newname (make-temp-name prefix))
282         (newprefix prefix))
283     (while (file-exists-p newname)
284       (setq newprefix (concat newprefix "x"))
285       (setq newname (make-temp-name newprefix)))
286     newname))
287
288 (defun mail-source-callback (callback info)
289   "Call CALLBACK on the mail file, and then remove the mail file.
290 Pass INFO on to CALLBACK."
291   (if (or (not (file-exists-p mail-source-crash-box))
292           (zerop (nth 7 (file-attributes mail-source-crash-box))))
293       (progn
294         (when (file-exists-p mail-source-crash-box)
295           (delete-file mail-source-crash-box))
296         0)
297     (prog1
298         (funcall callback mail-source-crash-box info)
299       (when (file-exists-p mail-source-crash-box)
300         ;; Delete or move the incoming mail out of the way.
301         (if mail-source-delete-incoming
302             (delete-file mail-source-crash-box)
303           (let ((incoming
304                  (mail-source-make-complex-temp-name
305                   (expand-file-name
306                    mail-source-incoming-file-prefix
307                    mail-source-directory))))
308             (unless (file-exists-p (file-name-directory incoming))
309               (make-directory (file-name-directory incoming) t))
310             (rename-file mail-source-crash-box incoming t)))))))
311
312 (defun mail-source-movemail (from to)
313   "Move FROM to TO using movemail."
314   (if (not (file-writable-p to))
315       (error "Can't write to crash box %s.  Not moving mail" to)
316     (let ((to (file-truename (expand-file-name to)))
317           errors result)
318       (setq to (file-truename to)
319             from (file-truename from))
320       ;; Set TO if have not already done so, and rename or copy
321       ;; the file FROM to TO if and as appropriate.
322       (cond
323        ((file-exists-p to)
324         ;; The crash box exists already.
325         t)
326        ((not (file-exists-p from))
327         ;; There is no inbox.
328         (setq to nil))
329        ((zerop (nth 7 (file-attributes from)))
330         ;; Empty file.
331         (setq to nil))
332        (t
333         ;; If getting from mail spool directory, use movemail to move
334         ;; rather than just renaming, so as to interlock with the
335         ;; mailer.
336         (unwind-protect
337             (save-excursion
338               (setq errors (generate-new-buffer " *mail source loss*"))
339               (let ((default-directory "/"))
340                 (setq result
341                       (apply
342                        'call-process
343                        (append
344                         (list
345                          (expand-file-name "movemail" exec-directory)
346                          nil errors nil from to)))))
347               (when (file-exists-p to)
348                 (set-file-modes to mail-source-default-file-modes))
349               (if (and (not (buffer-modified-p errors))
350                        (zerop result))
351                   ;; No output => movemail won.
352                   t
353                 (set-buffer errors)
354                 ;; There may be a warning about older revisions.  We
355                 ;; ignore that.
356                 (goto-char (point-min))
357                 (if (search-forward "older revision" nil t)
358                     t
359                   ;; Probably a real error.
360                   (subst-char-in-region (point-min) (point-max) ?\n ?\  )
361                   (goto-char (point-max))
362                   (skip-chars-backward " \t")
363                   (delete-region (point) (point-max))
364                   (goto-char (point-min))
365                   (when (looking-at "movemail: ")
366                     (delete-region (point-min) (match-end 0)))
367                   (unless (yes-or-no-p
368                            (format "movemail: %s (%d return).  Continue? "
369                                    (buffer-string) result))
370                     (error "%s" (buffer-string)))
371                   (setq to nil)))))))
372       (when (and errors
373                  (buffer-name errors))
374         (kill-buffer errors))
375       ;; Return whether we moved successfully or not.
376       to)))
377
378 (defun mail-source-movemail-and-remove (from to)
379   "Move FROM to TO using movemail, then remove FROM if empty."
380   (or (not (mail-source-movemail from to))
381       (not (zerop (nth 7 (file-attributes from))))
382       (delete-file from)))
383
384 (defvar mail-source-read-passwd nil)
385 (defun mail-source-read-passwd (prompt &rest args)
386   "Read a password using PROMPT.
387 If ARGS, PROMPT is used as an argument to `format'."
388   (let ((prompt
389          (if args
390              (apply 'format prompt args)
391            prompt)))
392     (unless mail-source-read-passwd
393       (if (or (fboundp 'read-passwd) (load "passwd" t))
394           (setq mail-source-read-passwd 'read-passwd)
395         (unless (fboundp 'ange-ftp-read-passwd)
396           (autoload 'ange-ftp-read-passwd "ange-ftp"))
397         (setq mail-source-read-passwd 'ange-ftp-read-passwd)))
398     (funcall mail-source-read-passwd prompt)))
399
400 (defun mail-source-fetch-with-program (program)
401   (zerop (call-process shell-file-name nil nil nil
402                        shell-command-switch program)))
403
404 (defun mail-source-run-script (script spec &optional delay)
405   (when script
406     (if (and (symbolp script) (fboundp script))
407         (funcall script)
408       (mail-source-call-script
409        (format-spec script spec))))
410   (when delay
411     (sleep-for delay)))
412
413 (defun mail-source-call-script (script)
414   (let ((background nil))
415     (when (string-match "& *$" script)
416       (setq script (substring script 0 (match-beginning 0))
417             background 0))
418     (call-process shell-file-name nil background nil
419                   shell-command-switch script)))
420
421 ;;;
422 ;;; Different fetchers
423 ;;;
424
425 (defun mail-source-fetch-file (source callback)
426   "Fetcher for single-file sources."
427   (mail-source-bind (file source)
428     (mail-source-run-script
429      prescript (format-spec-make ?t mail-source-crash-box)
430      prescript-delay)
431     (let ((mail-source-string (format "file:%s" path)))
432       (if (mail-source-movemail path mail-source-crash-box)
433           (prog1
434               (mail-source-callback callback path)
435             (mail-source-run-script
436              postscript (format-spec-make ?t mail-source-crash-box)))
437         0))))
438
439 (defun mail-source-fetch-directory (source callback)
440   "Fetcher for directory sources."
441   (mail-source-bind (directory source)
442     (let ((found 0)
443           (mail-source-string (format "directory:%s" path)))
444       (dolist (file (directory-files
445                      path t (concat (regexp-quote suffix) "$")))
446         (when (and (file-regular-p file)
447                    (funcall predicate file)
448                    (mail-source-movemail file mail-source-crash-box))
449           (incf found (mail-source-callback callback file))))
450       found)))
451
452 (defun mail-source-fetch-pop (source callback)
453   "Fetcher for single-file sources."
454   (mail-source-bind (pop source)
455     (mail-source-run-script
456      prescript
457      (format-spec-make ?p password ?t mail-source-crash-box
458                        ?s server ?P port ?u user)
459      prescript-delay)
460     (let ((from (format "%s:%s:%s" server user port))
461           (mail-source-string (format "pop:%s@%s" user server))
462           result)
463       (when (eq authentication 'password)
464         (setq password
465               (or password
466                   (cdr (assoc from mail-source-password-cache))
467                   (mail-source-read-passwd
468                    (format "Password for %s at %s: " user server)))))
469       (when server
470         (setenv "MAILHOST" server))
471       (setq result
472             (cond
473              (program
474               (mail-source-fetch-with-program
475                (format-spec
476                 program
477                 (format-spec-make ?p password ?t mail-source-crash-box
478                                   ?s server ?P port ?u user))))
479              (function
480               (funcall function mail-source-crash-box))
481              ;; The default is to use pop3.el.
482              (t
483               (let ((pop3-password password)
484                     (pop3-maildrop user)
485                     (pop3-mailhost server)
486                     (pop3-port port)
487                     (pop3-authentication-scheme
488                      (if (eq authentication 'apop) 'apop 'pass))
489                     (pop3-connection-type connection))
490                 (save-excursion (pop3-movemail mail-source-crash-box))))))
491       (if result
492           (progn
493             (when (eq authentication 'password)
494               (unless (assoc from mail-source-password-cache)
495                 (push (cons from password) mail-source-password-cache)))
496             (prog1
497                 (mail-source-callback callback server)
498               ;; Update display-time's mail flag, if relevant.
499               (if (equal source mail-source-primary-source)
500                   (setq mail-source-new-mail-available nil))
501               (mail-source-run-script
502                postscript
503                (format-spec-make ?p password ?t mail-source-crash-box
504                                  ?s server ?P port ?u user))))
505         ;; We nix out the password in case the error
506         ;; was because of a wrong password being given.
507         (setq mail-source-password-cache
508               (delq (assoc from mail-source-password-cache)
509                     mail-source-password-cache))
510         0))))
511
512 (defun mail-source-check-pop (source)
513   "Check whether there is new mail."
514   (mail-source-bind (pop source)
515     (let ((from (format "%s:%s:%s" server user port))
516           (mail-source-string (format "pop:%s@%s" user server))
517           result)
518       (when (eq authentication 'password)
519         (setq password
520               (or password
521                   (cdr (assoc from mail-source-password-cache))
522                   (mail-source-read-passwd
523                    (format "Password for %s at %s: " user server))))
524         (unless (assoc from mail-source-password-cache)
525           (push (cons from password) mail-source-password-cache)))
526       (when server
527         (setenv "MAILHOST" server))
528       (setq result
529             (cond
530              ;; No easy way to check whether mail is waiting for these.
531              (program)
532              (function)
533              ;; The default is to use pop3.el.
534              (t
535               (let ((pop3-password password)
536                     (pop3-maildrop user)
537                     (pop3-mailhost server)
538                     (pop3-port port)
539                     (pop3-authentication-scheme
540                      (if (eq authentication 'apop) 'apop 'pass)))
541                 (save-excursion (pop3-get-message-count))))))
542       (if result
543           ;; Inform display-time that we have new mail.
544           (setq mail-source-new-mail-available (> result 0))
545         ;; We nix out the password in case the error
546         ;; was because of a wrong password being given.
547         (setq mail-source-password-cache
548               (delq (assoc from mail-source-password-cache)
549                     mail-source-password-cache)))
550       result)))
551
552 (defun mail-source-new-mail-p ()
553   "Handler for `display-time' to indicate when new mail is available."
554   ;; Only report flag setting; flag is updated on a different schedule.
555   mail-source-new-mail-available)
556
557
558 (defvar mail-source-report-new-mail nil)
559 (defvar mail-source-report-new-mail-timer nil)
560 (defvar mail-source-report-new-mail-idle-timer nil)
561
562 (eval-when-compile (require 'timer))
563
564 (defun mail-source-start-idle-timer ()
565   ;; Start our idle timer if necessary, so we delay the check until the
566   ;; user isn't typing.
567   (unless mail-source-report-new-mail-idle-timer
568     (setq mail-source-report-new-mail-idle-timer
569           (run-with-idle-timer
570            mail-source-idle-time-delay
571            nil
572            (lambda ()
573              (setq mail-source-report-new-mail-idle-timer nil)
574              (mail-source-check-pop mail-source-primary-source))))
575     ;; Since idle timers created when Emacs is already in the idle
576     ;; state don't get activated until Emacs _next_ becomes idle, we
577     ;; need to force our timer to be considered active now.  We do
578     ;; this by being naughty and poking the timer internals directly
579     ;; (element 0 of the vector is nil if the timer is active).
580     (aset mail-source-report-new-mail-idle-timer 0 nil)))
581
582 (defun mail-source-report-new-mail (arg)
583   "Toggle whether to report when new mail is available.
584 This only works when `display-time' is enabled."
585   (interactive "P")
586   (if (not mail-source-primary-source)
587       (error "Need to set `mail-source-primary-source' to check for new mail."))
588   (let ((on (if (null arg)
589                 (not mail-source-report-new-mail)
590               (> (prefix-numeric-value arg) 0))))
591     (setq mail-source-report-new-mail on)
592     (and mail-source-report-new-mail-timer
593          (cancel-timer mail-source-report-new-mail-timer))
594     (and mail-source-report-new-mail-idle-timer
595          (cancel-timer mail-source-report-new-mail-idle-timer))
596     (setq mail-source-report-new-mail-timer nil)
597     (setq mail-source-report-new-mail-idle-timer nil)
598     (if on
599         (progn
600           (require 'time)
601           (setq display-time-mail-function #'mail-source-new-mail-p)
602           ;; Set up the main timer.
603           (setq mail-source-report-new-mail-timer
604                 (run-at-time t (* 60 mail-source-report-new-mail-interval)
605                              #'mail-source-start-idle-timer))
606           ;; When you get new mail, clear "Mail" from the mode line.
607           (add-hook 'nnmail-post-get-new-mail-hook
608                     'display-time-event-handler)
609           (message "Mail check enabled"))
610       (setq display-time-mail-function nil)
611       (remove-hook 'nnmail-post-get-new-mail-hook
612                    'display-time-event-handler)
613       (message "Mail check disabled"))))
614
615 (defun mail-source-fetch-maildir (source callback)
616   "Fetcher for maildir sources."
617   (mail-source-bind (maildir source)
618     (let ((found 0)
619           mail-source-string)
620       (unless (string-match "/$" path)
621         (setq path (concat path "/")))
622       (dolist (subdir subdirs)
623         (when (file-directory-p (concat path subdir))
624           (setq mail-source-string (format "maildir:%s%s" path subdir))
625           (dolist (file (directory-files (concat path subdir) t))
626             (when (and (not (file-directory-p file))
627                        (not (if function
628                                 (funcall function file mail-source-crash-box)
629                               (let ((coding-system-for-write
630                                      nnheader-text-coding-system)
631                                     (coding-system-for-read
632                                      nnheader-text-coding-system)
633                                     (output-coding-system
634                                      nnheader-text-coding-system)
635                                     (input-coding-system
636                                      nnheader-text-coding-system))
637                                 (with-temp-file mail-source-crash-box
638                                   (insert-file-contents file)
639                                   (goto-char (point-min))
640 ;;;                               ;; Unix mail format
641 ;;;                               (unless (looking-at "\n*From ")
642 ;;;                                 (insert "From maildir " 
643 ;;;                                         (current-time-string) "\n"))
644 ;;;                               (while (re-search-forward "^From " nil t)
645 ;;;                                 (replace-match ">From "))
646                                   ;; MMDF mail format
647                                   (insert "\001\001\001\001\n")
648                                   (goto-char (point-max))
649                                   (insert "\n\n"))
650                                 (delete-file file)))))
651               (incf found (mail-source-callback callback file))))))
652       found)))
653
654 (eval-and-compile
655   (autoload 'imap-open "imap")
656   (autoload 'imap-authenticate "imap")
657   (autoload 'imap-mailbox-select "imap")
658   (autoload 'imap-mailbox-unselect "imap")
659   (autoload 'imap-mailbox-close "imap")
660   (autoload 'imap-search "imap")
661   (autoload 'imap-fetch "imap")
662   (autoload 'imap-close "imap")
663   (autoload 'imap-error-text "imap")
664   (autoload 'imap-message-flags-add "imap")
665   (autoload 'imap-list-to-message-set "imap"))
666
667 (defun mail-source-fetch-imap (source callback)
668   "Fetcher for imap sources."
669   (mail-source-bind (imap source)
670     (let ((from (format "%s:%s:%s" server user port))
671           (found 0)
672           (buf (get-buffer-create
673                 (format " *imap source %s:%s:%s *" server user mailbox)))
674           (mail-source-string (format "imap:%s:%s" server mailbox))
675           remove)
676       (if (and (imap-open server port stream authentication buf)
677                (imap-authenticate
678                 user (or (cdr (assoc from mail-source-password-cache))
679                          password) buf)
680                (imap-mailbox-select mailbox nil buf))
681           (let (str
682                 (coding-system-for-write 'binary)
683                 (output-coding-system 'binary))
684             (with-temp-file mail-source-crash-box
685               ;; remember password
686               (with-current-buffer buf
687                 (when (or imap-password
688                           (assoc from mail-source-password-cache))
689                   (push (cons from imap-password) mail-source-password-cache)))
690               ;; if predicate is nil, use all uids
691               (dolist (uid (imap-search (or predicate "1:*") buf))
692                 (when (setq str (imap-fetch uid "RFC822.PEEK" 'RFC822 nil buf))
693                   (push uid remove)
694                   (insert "From imap " (current-time-string) "\n")
695                   (save-excursion
696                     (insert str "\n\n"))
697                   (while (re-search-forward "^From " nil t)
698                     (replace-match ">From "))
699                   (goto-char (point-max))))
700               (nnheader-ms-strip-cr))
701             (incf found (mail-source-callback callback server))
702             (when (and remove fetchflag)
703               (imap-message-flags-add
704                (imap-list-to-message-set remove) fetchflag nil buf))
705             (if dontexpunge
706                 (imap-mailbox-unselect buf)
707               (imap-mailbox-close buf))
708             (imap-close buf))
709         (imap-close buf)
710         ;; We nix out the password in case the error
711         ;; was because of a wrong password being given.
712         (setq mail-source-password-cache
713               (delq (assoc from mail-source-password-cache)
714                     mail-source-password-cache))
715         (error (imap-error-text buf)))
716       (kill-buffer buf)
717       found)))
718
719 (eval-and-compile
720   (autoload 'webmail-fetch "webmail"))
721
722 (defun mail-source-fetch-webmail (source callback)
723   "Fetch for webmail source."
724   (mail-source-bind (webmail source)
725     (let ((mail-source-string (format "webmail:%s:%s" subtype user))
726           (webmail-newmail-only dontexpunge)
727           (webmail-move-to-trash-can (not dontexpunge)))
728       (when (eq authentication 'password)
729         (setq password
730               (or password
731                   (cdr (assoc (format "webmail:%s:%s" subtype user) 
732                               mail-source-password-cache))
733                   (mail-source-read-passwd
734                    (format "Password for %s at %s: " user subtype))))
735         (when (and password
736                    (not (assoc (format "webmail:%s:%s" subtype user) 
737                                mail-source-password-cache)))
738           (push (cons (format "webmail:%s:%s" subtype user) password) 
739                 mail-source-password-cache)))
740       (webmail-fetch mail-source-crash-box subtype user password)
741       (mail-source-callback callback (symbol-name subtype)))))
742
743 (provide 'mail-source)
744
745 ;;; mail-source.el ends here