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