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