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