Synch to No Gnus 200510111141.
[elisp/gnus.git-] / lisp / mail-source.el
1 ;;; mail-source.el --- functions for fetching mail
2
3 ;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004,
4 ;;   2005 Free Software Foundation, Inc.
5
6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;; Keywords: news, mail
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 ;; Boston, MA 02110-1301, USA.
25
26 ;;; Commentary:
27
28 ;;; Code:
29
30 (eval-when-compile
31   (require 'cl)
32   (require 'imap)
33   (defvar display-time-mail-function))
34 (eval-and-compile
35   (autoload 'pop3-movemail "pop3")
36   (autoload 'pop3-get-message-count "pop3")
37   (autoload 'nnheader-cancel-timer "nnheader"))
38 (require 'format-spec)
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   :version "22.1"
239   :group 'mail-source
240   :type 'boolean)
241
242 (defcustom mail-source-primary-source nil
243   "*Primary source for incoming mail.
244 If non-nil, this maildrop will be checked periodically for new mail."
245   :group 'mail-source
246   :type 'sexp)
247
248 (defcustom mail-source-flash t
249   "*If non-nil, flash periodically when mail is available."
250   :group 'mail-source
251   :type 'boolean)
252
253 (defcustom mail-source-crash-box "~/.emacs-mail-crash-box"
254   "File where mail will be stored while processing it."
255   :group 'mail-source
256   :type 'file)
257
258 (defcustom mail-source-directory message-directory
259   "Directory where incoming mail source files (if any) will be stored."
260   :group 'mail-source
261   :type 'directory)
262
263 (defcustom mail-source-default-file-modes 384
264   "Set the mode bits of all new mail files to this integer."
265   :group 'mail-source
266   :type 'integer)
267
268 (defcustom mail-source-delete-incoming nil
269   "*If non-nil, delete incoming files after handling.
270 If t, delete immediately, if nil, never delete.  If a positive number, delete
271 files older than number of days."
272   ;; Note: The removing happens in `mail-source-callback', i.e. no old
273   ;; incoming files will be deleted, unless you receive new mail.
274   ;;
275   ;; You may also set this to `nil' and call `mail-source-delete-old-incoming'
276   ;; from a hook or interactively.
277   :group 'mail-source
278   :type '(choice (const :tag "immediately" t)
279                  (const :tag "never" nil)
280                  (integer :tag "days")))
281
282 (defcustom mail-source-delete-old-incoming-confirm t
283   "*If non-nil, ask for for confirmation before deleting old incoming files.
284 This variable only applies when `mail-source-delete-incoming' is a positive
285 number."
286   :version "22.1"
287   :group 'mail-source
288   :type 'boolean)
289
290 (defcustom mail-source-incoming-file-prefix "Incoming"
291   "Prefix for file name for storing incoming mail"
292   :group 'mail-source
293   :type 'string)
294
295 (defcustom mail-source-report-new-mail-interval 5
296   "Interval in minutes between checks for new mail."
297   :group 'mail-source
298   :type 'number)
299
300 (defcustom mail-source-idle-time-delay 5
301   "Number of idle seconds to wait before checking for new mail."
302   :group 'mail-source
303   :type 'number)
304
305 (defcustom mail-source-movemail-program nil
306   "If non-nil, name of program for fetching new mail."
307   :version "22.1"
308   :group 'mail-source
309   :type '(choice (const nil) string))
310
311 ;;; Internal variables.
312
313 (defvar mail-source-string ""
314   "A dynamically bound string that says what the current mail source is.")
315
316 (defvar mail-source-new-mail-available nil
317   "Flag indicating when new mail is available.")
318
319 (eval-and-compile
320   (defvar mail-source-common-keyword-map
321     '((:plugged))
322     "Mapping from keywords to default values.
323 Common keywords should be listed here.")
324
325   (defvar mail-source-keyword-map
326     '((file
327        (:prescript)
328        (:prescript-delay)
329        (:postscript)
330        (:path (or (getenv "MAIL")
331                   (expand-file-name (user-login-name) rmail-spool-directory))))
332       (directory
333        (:prescript)
334        (:prescript-delay)
335        (:postscript)
336        (:path)
337        (:suffix ".spool")
338        (:predicate identity))
339       (pop
340        (:prescript)
341        (:prescript-delay)
342        (:postscript)
343        (:server (getenv "MAILHOST"))
344        (:port 110)
345        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
346        (:program)
347        (:function)
348        (:password)
349        (:connection)
350        (:authentication password)
351        (:leave))
352       (maildir
353        (:path (or (getenv "MAILDIR") "~/Maildir/"))
354        (:subdirs ("cur" "new"))
355        (:function))
356       (imap
357        (:server (getenv "MAILHOST"))
358        (:port)
359        (:stream)
360        (:program)
361        (:authentication)
362        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
363        (:password)
364        (:mailbox "INBOX")
365        (:predicate "UNSEEN UNDELETED")
366        (:fetchflag "\\Deleted")
367        (:prescript)
368        (:prescript-delay)
369        (:postscript)
370        (:dontexpunge))
371       (webmail
372        (:subtype hotmail)
373        (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
374        (:password)
375        (:dontexpunge)
376        (:authentication password)))
377     "Mapping from keywords to default values.
378 All keywords that can be used must be listed here."))
379
380 (defvar mail-source-fetcher-alist
381   '((file mail-source-fetch-file)
382     (directory mail-source-fetch-directory)
383     (pop mail-source-fetch-pop)
384     (maildir mail-source-fetch-maildir)
385     (imap mail-source-fetch-imap)
386     (webmail mail-source-fetch-webmail))
387   "A mapping from source type to fetcher function.")
388
389 (defvar mail-source-password-cache nil)
390
391 (defvar mail-source-plugged t)
392
393 ;;; Functions
394
395 (eval-and-compile
396   (defun mail-source-strip-keyword (keyword)
397     "Strip the leading colon off the KEYWORD."
398     (intern (substring (symbol-name keyword) 1))))
399
400 (eval-and-compile
401   (defun mail-source-bind-1 (type)
402     (let* ((defaults (cdr (assq type mail-source-keyword-map)))
403            default bind)
404       (while (setq default (pop defaults))
405         (push (list (mail-source-strip-keyword (car default))
406                     nil)
407               bind))
408       bind)))
409
410 (defmacro mail-source-bind (type-source &rest body)
411   "Return a `let' form that binds all variables in source TYPE.
412 TYPE-SOURCE is a list where the first element is the TYPE, and
413 the second variable is the SOURCE.
414 At run time, the mail source specifier SOURCE will be inspected,
415 and the variables will be set according to it.  Variables not
416 specified will be given default values.
417
418 After this is done, BODY will be executed in the scope
419 of the `let' form.
420
421 The variables bound and their default values are described by
422 the `mail-source-keyword-map' variable."
423   `(let ,(mail-source-bind-1 (car type-source))
424      (mail-source-set-1 ,(cadr type-source))
425      ,@body))
426
427 (put 'mail-source-bind 'lisp-indent-function 1)
428 (put 'mail-source-bind 'edebug-form-spec '(sexp body))
429
430 (defun mail-source-set-1 (source)
431   (let* ((type (pop source))
432          (defaults (cdr (assq type mail-source-keyword-map)))
433          default value keyword)
434     (while (setq default (pop defaults))
435       (set (mail-source-strip-keyword (setq keyword (car default)))
436            (if (setq value (plist-get source keyword))
437                (mail-source-value value)
438              (mail-source-value (cadr default)))))))
439
440 (eval-and-compile
441   (defun mail-source-bind-common-1 ()
442     (let* ((defaults mail-source-common-keyword-map)
443            default bind)
444       (while (setq default (pop defaults))
445         (push (list (mail-source-strip-keyword (car default))
446                     nil)
447               bind))
448       bind)))
449
450 (defun mail-source-set-common-1 (source)
451   (let* ((type (pop source))
452          (defaults mail-source-common-keyword-map)
453          (defaults-1 (cdr (assq type mail-source-keyword-map)))
454          default value keyword)
455     (while (setq default (pop defaults))
456       (set (mail-source-strip-keyword (setq keyword (car default)))
457            (if (setq value (plist-get source keyword))
458                (mail-source-value value)
459              (if (setq value (assq  keyword defaults-1))
460                  (mail-source-value (cadr value))
461                (mail-source-value (cadr default))))))))
462
463 (defmacro mail-source-bind-common (source &rest body)
464   "Return a `let' form that binds all common variables.
465 See `mail-source-bind'."
466   `(let ,(mail-source-bind-common-1)
467      (mail-source-set-common-1 source)
468      ,@body))
469
470 (put 'mail-source-bind-common 'lisp-indent-function 1)
471 (put 'mail-source-bind-common 'edebug-form-spec '(sexp body))
472
473 (defun mail-source-value (value)
474   "Return the value of VALUE."
475   (cond
476    ;; String
477    ((stringp value)
478     value)
479    ;; Function
480    ((and (listp value)
481          (functionp (car value)))
482     (eval value))
483    ;; Just return the value.
484    (t
485     value)))
486
487 (defun mail-source-fetch (source callback)
488   "Fetch mail from SOURCE and call CALLBACK zero or more times.
489 CALLBACK will be called with the name of the file where (some of)
490 the mail from SOURCE is put.
491 Return the number of files that were found."
492   (mail-source-bind-common source
493     (if (or mail-source-plugged plugged)
494         (save-excursion
495           (let ((function (cadr (assq (car source) mail-source-fetcher-alist)))
496                 (found 0))
497             (unless function
498               (error "%S is an invalid mail source specification" source))
499             ;; If there's anything in the crash box, we do it first.
500             (when (file-exists-p mail-source-crash-box)
501               (message "Processing mail from %s..." mail-source-crash-box)
502               (setq found (mail-source-callback
503                            callback mail-source-crash-box))
504               (mail-source-delete-crash-box))
505             (+ found
506                (if (or debug-on-quit debug-on-error)
507                    (funcall function source callback)
508                  (condition-case err
509                      (funcall function source callback)
510                    (error
511                     (if (and (not mail-source-ignore-errors)
512                              (not
513                               (yes-or-no-p
514                                (format "Mail source %s error (%s).  Continue? "
515                                        (if (memq ':password source)
516                                            (let ((s (copy-sequence source)))
517                                              (setcar (cdr (memq ':password s))
518                                                      "********")
519                                              s)
520                                          source)
521                                        (cadr err)))))
522                       (error "Cannot get new mail"))
523                     0)))))))))
524
525 (defun mail-source-delete-old-incoming (&optional age confirm)
526   "Remove incoming files older than AGE days.
527 If CONFIRM is non-nil, ask for confirmation before removing a file."
528   (interactive "P")
529   (let* ((high2days (/ 65536.0 60 60 24));; convert high bits to days
530          (low2days  (/ 1.0 65536.0))     ;; convert low bits to days
531          (diff (if (natnump age) age 30));; fallback, if no valid AGE given
532          currday files)
533     (setq files (directory-files
534                  mail-source-directory t
535                  (concat mail-source-incoming-file-prefix "*"))
536           currday (* (car (current-time)) high2days)
537           currday (+ currday (* low2days (nth 1 (current-time)))))
538     (while files
539       (let* ((ffile (car files))
540              (bfile (gnus-replace-in-string
541                      ffile "\\`.*/\\([^/]+\\)\\'" "\\1"))
542              (filetime (nth 5 (file-attributes ffile)))
543              (fileday (* (car filetime) high2days))
544              (fileday (+ fileday (* low2days (nth 1 filetime)))))
545         (setq files (cdr files))
546         (when (and (> (- currday fileday) diff)
547                    (gnus-message 8 "File `%s' is older than %s day(s)"
548                                  bfile diff)
549                    (or (not confirm)
550                        (y-or-n-p (concat "Remove file `" bfile "'? "))))
551           (delete-file ffile))))))
552
553 (defun mail-source-callback (callback info)
554   "Call CALLBACK on the mail file.  Pass INFO on to CALLBACK."
555   (if (or (not (file-exists-p mail-source-crash-box))
556           (zerop (nth 7 (file-attributes mail-source-crash-box))))
557       (progn
558         (when (file-exists-p mail-source-crash-box)
559           (delete-file mail-source-crash-box))
560         0)
561     (funcall callback mail-source-crash-box info)))
562
563 (defun mail-source-delete-crash-box ()
564   (when (file-exists-p mail-source-crash-box)
565     ;; Delete or move the incoming mail out of the way.
566     (if (eq mail-source-delete-incoming t)
567         (delete-file mail-source-crash-box)
568       (let ((incoming
569              (mm-make-temp-file
570               (expand-file-name
571                mail-source-incoming-file-prefix
572                mail-source-directory))))
573         (unless (file-exists-p (file-name-directory incoming))
574           (make-directory (file-name-directory incoming) t))
575         (rename-file mail-source-crash-box incoming t)
576         ;; remove old incoming files?
577         (when (natnump mail-source-delete-incoming)
578           (mail-source-delete-old-incoming
579            mail-source-delete-incoming
580            mail-source-delete-old-incoming-confirm))))))
581
582 (defun mail-source-movemail (from to)
583   "Move FROM to TO using movemail."
584   (if (not (file-writable-p to))
585       (error "Can't write to crash box %s.  Not moving mail" to)
586     (let ((to (file-truename (expand-file-name to)))
587           errors result)
588       (setq to (file-truename to)
589             from (file-truename from))
590       ;; Set TO if have not already done so, and rename or copy
591       ;; the file FROM to TO if and as appropriate.
592       (cond
593        ((file-exists-p to)
594         ;; The crash box exists already.
595         t)
596        ((not (file-exists-p from))
597         ;; There is no inbox.
598         (setq to nil))
599        ((zerop (nth 7 (file-attributes from)))
600         ;; Empty file.
601         (setq to nil))
602        (t
603         ;; If getting from mail spool directory, use movemail to move
604         ;; rather than just renaming, so as to interlock with the
605         ;; mailer.
606         (unwind-protect
607             (save-excursion
608               (setq errors (generate-new-buffer " *mail source loss*"))
609               (let ((default-directory "/"))
610                 (setq result
611                       (apply
612                        'call-process
613                        (append
614                         (list
615                          (or mail-source-movemail-program
616                              (expand-file-name "movemail" exec-directory))
617                          nil errors nil from to)))))
618               (when (file-exists-p to)
619                 (set-file-modes to mail-source-default-file-modes))
620               (if (and (or (not (buffer-modified-p errors))
621                            (zerop (buffer-size errors)))
622                        (and (numberp result)
623                             (zerop result)))
624                   ;; No output => movemail won.
625                   t
626                 (set-buffer errors)
627                 ;; There may be a warning about older revisions.  We
628                 ;; ignore that.
629                 (goto-char (point-min))
630                 (if (search-forward "older revision" nil t)
631                     t
632                   ;; Probably a real error.
633                   (subst-char-in-region (point-min) (point-max) ?\n ?\  )
634                   (goto-char (point-max))
635                   (skip-chars-backward " \t")
636                   (delete-region (point) (point-max))
637                   (goto-char (point-min))
638                   (when (looking-at "movemail: ")
639                     (delete-region (point-min) (match-end 0)))
640                   ;; Result may be a signal description string.
641                   (unless (yes-or-no-p
642                            (format "movemail: %s (%s return).  Continue? "
643                                    (buffer-string) result))
644                     (error "%s" (buffer-string)))
645                   (setq to nil)))))))
646       (when (and errors
647                  (buffer-name errors))
648         (kill-buffer errors))
649       ;; Return whether we moved successfully or not.
650       to)))
651
652 (defun mail-source-movemail-and-remove (from to)
653   "Move FROM to TO using movemail, then remove FROM if empty."
654   (or (not (mail-source-movemail from to))
655       (not (zerop (nth 7 (file-attributes from))))
656       (delete-file from)))
657
658 (defun mail-source-fetch-with-program (program)
659   (eq 0 (call-process shell-file-name nil nil nil
660                       shell-command-switch program)))
661
662 (defun mail-source-run-script (script spec &optional delay)
663   (when script
664     (if (functionp script)
665         (funcall script)
666       (mail-source-call-script
667        (format-spec script spec))))
668   (when delay
669     (sleep-for delay)))
670
671 (defun mail-source-call-script (script)
672   (let ((background nil))
673     (when (string-match "& *$" script)
674       (setq script (substring script 0 (match-beginning 0))
675             background 0))
676     (call-process shell-file-name nil background nil
677                   shell-command-switch script)))
678
679 ;;;
680 ;;; Different fetchers
681 ;;;
682
683 (defun mail-source-fetch-file (source callback)
684   "Fetcher for single-file sources."
685   (mail-source-bind (file source)
686     (mail-source-run-script
687      prescript (format-spec-make ?t mail-source-crash-box)
688      prescript-delay)
689     (let ((mail-source-string (format "file:%s" path)))
690       (if (mail-source-movemail path mail-source-crash-box)
691           (prog1
692               (mail-source-callback callback path)
693             (mail-source-run-script
694              postscript (format-spec-make ?t mail-source-crash-box))
695             (mail-source-delete-crash-box))
696         0))))
697
698 (defun mail-source-fetch-directory (source callback)
699   "Fetcher for directory sources."
700   (mail-source-bind (directory source)
701     (mail-source-run-script
702      prescript (format-spec-make ?t path) prescript-delay)
703     (let ((found 0)
704           (mail-source-string (format "directory:%s" path)))
705       (dolist (file (directory-files
706                      path t (concat (regexp-quote suffix) "$")))
707         (when (and (file-regular-p file)
708                    (funcall predicate file)
709                    (mail-source-movemail file mail-source-crash-box))
710           (incf found (mail-source-callback callback file))
711           (mail-source-run-script postscript (format-spec-make ?t path))
712           (mail-source-delete-crash-box)))
713       found)))
714
715 (defun mail-source-fetch-pop (source callback)
716   "Fetcher for single-file sources."
717   (mail-source-bind (pop source)
718     (mail-source-run-script
719      prescript
720      (format-spec-make ?p password ?t mail-source-crash-box
721                        ?s server ?P port ?u user)
722      prescript-delay)
723     (let ((from (format "%s:%s:%s" server user port))
724           (mail-source-string (format "pop:%s@%s" user server))
725           result)
726       (when (eq authentication 'password)
727         (setq password
728               (or password
729                   (cdr (assoc from mail-source-password-cache))
730                   (read-passwd
731                    (format "Password for %s at %s: " user server)))))
732       (when server
733         (setenv "MAILHOST" server))
734       (setq result
735             (cond
736              (program
737               (mail-source-fetch-with-program
738                (format-spec
739                 program
740                 (format-spec-make ?p password ?t mail-source-crash-box
741                                   ?s server ?P port ?u user))))
742              (function
743               (funcall function mail-source-crash-box))
744              ;; The default is to use pop3.el.
745              (t
746               (require 'pop3)
747               (let ((pop3-password password)
748                     (pop3-maildrop user)
749                     (pop3-mailhost server)
750                     (pop3-port port)
751                     (pop3-authentication-scheme
752                      (if (eq authentication 'apop) 'apop 'pass))
753                     (pop3-connection-type connection)
754                     (pop3-leave-mail-on-server
755                      (or leave
756                          (and (boundp 'pop3-leave-mail-on-server)
757                               (symbol-value 'pop3-leave-mail-on-server)))))
758                 (if (or debug-on-quit debug-on-error)
759                     (save-excursion (pop3-movemail mail-source-crash-box))
760                   (condition-case err
761                       (save-excursion (pop3-movemail mail-source-crash-box))
762                     (error
763                      ;; We nix out the password in case the error
764                      ;; was because of a wrong password being given.
765                      (setq mail-source-password-cache
766                            (delq (assoc from mail-source-password-cache)
767                                  mail-source-password-cache))
768                      (signal (car err) (cdr err)))))))))
769       (if result
770           (progn
771             (when (eq authentication 'password)
772               (unless (assoc from mail-source-password-cache)
773                 (push (cons from password) mail-source-password-cache)))
774             (prog1
775                 (mail-source-callback callback server)
776               ;; Update display-time's mail flag, if relevant.
777               (if (equal source mail-source-primary-source)
778                   (setq mail-source-new-mail-available nil))
779               (mail-source-run-script
780                postscript
781                (format-spec-make ?p password ?t mail-source-crash-box
782                                  ?s server ?P port ?u user))
783               (mail-source-delete-crash-box)))
784         ;; We nix out the password in case the error
785         ;; was because of a wrong password being given.
786         (setq mail-source-password-cache
787               (delq (assoc from mail-source-password-cache)
788                     mail-source-password-cache))
789         0))))
790
791 (defun mail-source-check-pop (source)
792   "Check whether there is new mail."
793   (mail-source-bind (pop source)
794     (let ((from (format "%s:%s:%s" server user port))
795           (mail-source-string (format "pop:%s@%s" user server))
796           result)
797       (when (eq authentication 'password)
798         (setq password
799               (or password
800                   (cdr (assoc from mail-source-password-cache))
801                   (read-passwd
802                    (format "Password for %s at %s: " user server))))
803         (unless (assoc from mail-source-password-cache)
804           (push (cons from password) mail-source-password-cache)))
805       (when server
806         (setenv "MAILHOST" server))
807       (setq result
808             (cond
809              ;; No easy way to check whether mail is waiting for these.
810              (program)
811              (function)
812              ;; The default is to use pop3.el.
813              (t
814               (require 'pop3)
815               (let ((pop3-password password)
816                     (pop3-maildrop user)
817                     (pop3-mailhost server)
818                     (pop3-port port)
819                     (pop3-authentication-scheme
820                      (if (eq authentication 'apop) 'apop 'pass)))
821                 (if (or debug-on-quit debug-on-error)
822                     (save-excursion (pop3-get-message-count))
823                   (condition-case err
824                       (save-excursion (pop3-get-message-count))
825                     (error
826                      ;; We nix out the password in case the error
827                      ;; was because of a wrong password being given.
828                      (setq mail-source-password-cache
829                            (delq (assoc from mail-source-password-cache)
830                                  mail-source-password-cache))
831                      (signal (car err) (cdr err)))))))))
832       (if result
833           ;; Inform display-time that we have new mail.
834           (setq mail-source-new-mail-available (> result 0))
835         ;; We nix out the password in case the error
836         ;; was because of a wrong password being given.
837         (setq mail-source-password-cache
838               (delq (assoc from mail-source-password-cache)
839                     mail-source-password-cache)))
840       result)))
841
842 (defun mail-source-touch-pop ()
843   "Open and close a POP connection shortly.
844 POP server should be defined in `mail-source-primary-source' (which is
845 preferred) or `mail-sources'.  You may use it for the POP-before-SMTP
846 authentication.  To do that, you need to set the
847 `message-send-mail-function' variable as `message-send-mail-with-smtp'
848 or `message-smtpmail-send-it' and put the following line in your
849 ~/.gnus.el file:
850
851 \(add-hook 'message-send-mail-hook 'mail-source-touch-pop)
852
853 See the Gnus manual for details."
854   (let ((sources (if mail-source-primary-source
855                      (list mail-source-primary-source)
856                    mail-sources)))
857     (while sources
858       (if (eq 'pop (car (car sources)))
859           (mail-source-check-pop (car sources)))
860       (setq sources (cdr sources)))))
861
862 (defun mail-source-new-mail-p ()
863   "Handler for `display-time' to indicate when new mail is available."
864   ;; Flash (ie. ring the visible bell) if mail is available.
865   (if (and mail-source-flash mail-source-new-mail-available)
866       (let ((visible-bell t))
867         (ding)))
868   ;; Only report flag setting; flag is updated on a different schedule.
869   mail-source-new-mail-available)
870
871
872 (defvar mail-source-report-new-mail nil)
873 (defvar mail-source-report-new-mail-timer nil)
874 (defvar mail-source-report-new-mail-idle-timer nil)
875
876 (eval-when-compile
877   (if (featurep 'xemacs)
878       (require 'timer-funcs)
879     (require 'timer)))
880
881 (defun mail-source-start-idle-timer ()
882   ;; Start our idle timer if necessary, so we delay the check until the
883   ;; user isn't typing.
884   (unless mail-source-report-new-mail-idle-timer
885     (setq mail-source-report-new-mail-idle-timer
886           (run-with-idle-timer
887            mail-source-idle-time-delay
888            nil
889            (lambda ()
890              (unwind-protect
891                  (mail-source-check-pop mail-source-primary-source)
892                (setq mail-source-report-new-mail-idle-timer nil)))))
893     ;; Since idle timers created when Emacs is already in the idle
894     ;; state don't get activated until Emacs _next_ becomes idle, we
895     ;; need to force our timer to be considered active now.  We do
896     ;; this by being naughty and poking the timer internals directly
897     ;; (element 0 of the vector is nil if the timer is active).
898     (aset mail-source-report-new-mail-idle-timer 0 nil)))
899
900 (defun mail-source-report-new-mail (arg)
901   "Toggle whether to report when new mail is available.
902 This only works when `display-time' is enabled."
903   (interactive "P")
904   (if (not mail-source-primary-source)
905       (error "Need to set `mail-source-primary-source' to check for new mail"))
906   (let ((on (if (null arg)
907                 (not mail-source-report-new-mail)
908               (> (prefix-numeric-value arg) 0))))
909     (setq mail-source-report-new-mail on)
910     (and mail-source-report-new-mail-timer
911          (nnheader-cancel-timer mail-source-report-new-mail-timer))
912     (and mail-source-report-new-mail-idle-timer
913          (nnheader-cancel-timer mail-source-report-new-mail-idle-timer))
914     (setq mail-source-report-new-mail-timer nil)
915     (setq mail-source-report-new-mail-idle-timer nil)
916     (if on
917         (progn
918           (require 'time)
919           ;; display-time-mail-function is an Emacs 21 feature.
920           (setq display-time-mail-function #'mail-source-new-mail-p)
921           ;; Set up the main timer.
922           (setq mail-source-report-new-mail-timer
923                 (run-at-time
924                  (* 60 mail-source-report-new-mail-interval)
925                  (* 60 mail-source-report-new-mail-interval)
926                  #'mail-source-start-idle-timer))
927           ;; When you get new mail, clear "Mail" from the mode line.
928           (add-hook 'nnmail-post-get-new-mail-hook
929                     'display-time-event-handler)
930           (message "Mail check enabled"))
931       (setq display-time-mail-function nil)
932       (remove-hook 'nnmail-post-get-new-mail-hook
933                    'display-time-event-handler)
934       (message "Mail check disabled"))))
935
936 (defun mail-source-fetch-maildir (source callback)
937   "Fetcher for maildir sources."
938   (mail-source-bind (maildir source)
939     (let ((found 0)
940           mail-source-string)
941       (unless (string-match "/$" path)
942         (setq path (concat path "/")))
943       (dolist (subdir subdirs)
944         (when (file-directory-p (concat path subdir))
945           (setq mail-source-string (format "maildir:%s%s" path subdir))
946           (dolist (file (directory-files (concat path subdir) t))
947             (when (and (not (file-directory-p file))
948                        (not (if function
949                                 (funcall function file mail-source-crash-box)
950                               (let ((coding-system-for-write
951                                      nnheader-text-coding-system)
952                                     (coding-system-for-read
953                                      nnheader-text-coding-system))
954                                 (with-temp-file mail-source-crash-box
955                                   (insert-file-contents file)
956                                   (goto-char (point-min))
957 ;;;                               ;; Unix mail format
958 ;;;                               (unless (looking-at "\n*From ")
959 ;;;                                 (insert "From maildir "
960 ;;;                                         (current-time-string) "\n"))
961 ;;;                               (while (re-search-forward "^From " nil t)
962 ;;;                                 (replace-match ">From "))
963 ;;;                               (goto-char (point-max))
964 ;;;                               (insert "\n\n")
965                                   ;; MMDF mail format
966                                   (insert "\001\001\001\001\n"))
967                                 (delete-file file)))))
968               (incf found (mail-source-callback callback file))
969               (mail-source-delete-crash-box)))))
970       found)))
971
972 (eval-and-compile
973   (autoload 'imap-open "imap")
974   (autoload 'imap-authenticate "imap")
975   (autoload 'imap-mailbox-select "imap")
976   (autoload 'imap-mailbox-unselect "imap")
977   (autoload 'imap-mailbox-close "imap")
978   (autoload 'imap-search "imap")
979   (autoload 'imap-fetch "imap")
980   (autoload 'imap-close "imap")
981   (autoload 'imap-error-text "imap")
982   (autoload 'imap-message-flags-add "imap")
983   (autoload 'imap-list-to-message-set "imap")
984   (autoload 'imap-range-to-message-set "imap"))
985
986 (defvar mail-source-imap-file-coding-system 'binary
987   "Coding system for the crashbox made by `mail-source-fetch-imap'.")
988
989 (defun mail-source-fetch-imap (source callback)
990   "Fetcher for imap sources."
991   (mail-source-bind (imap source)
992     (mail-source-run-script
993      prescript (format-spec-make ?p password ?t mail-source-crash-box
994                                  ?s server ?P port ?u user)
995      prescript-delay)
996     (let ((from (format "%s:%s:%s" server user port))
997           (found 0)
998           (buf (generate-new-buffer " *imap source*"))
999           (mail-source-string (format "imap:%s:%s" server mailbox))
1000           (imap-shell-program (or (list program) imap-shell-program))
1001           remove)
1002       (if (and (imap-open server port stream authentication buf)
1003                (imap-authenticate
1004                 user (or (cdr (assoc from mail-source-password-cache))
1005                          password) buf)
1006                (imap-mailbox-select mailbox nil buf))
1007           (let ((coding-system-for-write mail-source-imap-file-coding-system)
1008                 str)
1009             (with-temp-file mail-source-crash-box
1010               ;; Avoid converting 8-bit chars from inserted strings to
1011               ;; multibyte.
1012               (set-buffer-multibyte nil)
1013               ;; remember password
1014               (with-current-buffer buf
1015                 (when (and imap-password
1016                            (not (assoc from mail-source-password-cache)))
1017                   (push (cons from imap-password) mail-source-password-cache)))
1018               ;; if predicate is nil, use all uids
1019               (dolist (uid (imap-search (or predicate "1:*") buf))
1020                 (when (setq str
1021                             (if (imap-capability 'IMAP4rev1 buf)
1022                                 (caddar (imap-fetch uid "BODY.PEEK[]"
1023                                                     'BODYDETAIL nil buf))
1024                               (imap-fetch uid "RFC822.PEEK" 'RFC822 nil buf)))
1025                   (push uid remove)
1026                   (insert "From imap " (current-time-string) "\n")
1027                   (save-excursion
1028                     (insert str "\n\n"))
1029                   (while (let ((case-fold-search nil))
1030                            (re-search-forward "^From " nil t))
1031                     (replace-match ">From "))
1032                   (goto-char (point-max))))
1033               (nnheader-ms-strip-cr))
1034             (incf found (mail-source-callback callback server))
1035             (mail-source-delete-crash-box)
1036             (when (and remove fetchflag)
1037               (setq remove (nreverse remove))
1038               (imap-message-flags-add
1039                (imap-range-to-message-set (gnus-compress-sequence remove))
1040                fetchflag nil buf))
1041             (if dontexpunge
1042                 (imap-mailbox-unselect buf)
1043               (imap-mailbox-close nil buf))
1044             (imap-close buf))
1045         (imap-close buf)
1046         ;; We nix out the password in case the error
1047         ;; was because of a wrong password being given.
1048         (setq mail-source-password-cache
1049               (delq (assoc from mail-source-password-cache)
1050                     mail-source-password-cache))
1051         (error "IMAP error: %s" (imap-error-text buf)))
1052       (kill-buffer buf)
1053       (mail-source-run-script
1054        postscript
1055        (format-spec-make ?p password ?t mail-source-crash-box
1056                          ?s server ?P port ?u user))
1057       found)))
1058
1059 (eval-and-compile
1060   (autoload 'webmail-fetch "webmail"))
1061
1062 (defun mail-source-fetch-webmail (source callback)
1063   "Fetch for webmail source."
1064   (mail-source-bind (webmail source)
1065     (let ((mail-source-string (format "webmail:%s:%s" subtype user))
1066           (webmail-newmail-only dontexpunge)
1067           (webmail-move-to-trash-can (not dontexpunge)))
1068       (when (eq authentication 'password)
1069         (setq password
1070               (or password
1071                   (cdr (assoc (format "webmail:%s:%s" subtype user)
1072                               mail-source-password-cache))
1073                   (read-passwd
1074                    (format "Password for %s at %s: " user subtype))))
1075         (when (and password
1076                    (not (assoc (format "webmail:%s:%s" subtype user)
1077                                mail-source-password-cache)))
1078           (push (cons (format "webmail:%s:%s" subtype user) password)
1079                 mail-source-password-cache)))
1080       (webmail-fetch mail-source-crash-box subtype user password)
1081       (mail-source-callback callback (symbol-name subtype))
1082       (mail-source-delete-crash-box))))
1083
1084 (provide 'mail-source)
1085
1086 ;;; mail-source.el ends here