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