delete_temp_file fix
[bpt/emacs.git] / lisp / gnus / mail-source.el
CommitLineData
c113de23 1;;; mail-source.el --- functions for fetching mail
e84b4b86 2
ba318903 3;; Copyright (C) 1999-2014 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
08545d0a 27(require 'format-spec)
4f926b3e
DL
28(eval-when-compile
29 (require 'cl)
9efa445f 30 (require 'imap))
b8e0f0cd 31(autoload 'auth-source-search "auth-source")
e574f629 32(autoload 'pop3-movemail "pop3")
8abf1b22
GM
33(autoload 'pop3-get-message-count "pop3")
34(autoload 'nnheader-cancel-timer "nnheader")
4f926b3e 35(require 'mm-util)
23f87bed 36(require 'message) ;; for `message-directory'
c113de23 37
9efa445f
DN
38(defvar display-time-mail-function)
39
c113de23
GM
40(defgroup mail-source nil
41 "The mail-fetching library."
ce9401f3 42 :version "21.1"
c113de23
GM
43 :group 'gnus)
44
4f926b3e
DL
45;; Define these at compile time to avoid dragging in imap always.
46(defconst mail-source-imap-authenticators
47 (eval-when-compile
48 (mapcar (lambda (a)
49 (list 'const (car a)))
50 imap-authenticator-alist)))
51(defconst mail-source-imap-streams
52 (eval-when-compile
53 (mapcar (lambda (a)
54 (list 'const (car a)))
55 imap-stream-alist)))
56
b890d447
MB
57(defcustom mail-sources '((file))
58 "Where the mail backends will look for incoming mail.
ce9401f3
DL
59This variable is a list of mail source specifiers.
60See Info node `(gnus)Mail Source Specifiers'."
c113de23 61 :group 'mail-source
a71e2379 62 :version "24.4"
23f87bed 63 :link '(custom-manual "(gnus)Mail Source Specifiers")
26c9afc3 64 :type `(choice
b890d447
MB
65 (const :tag "None" nil)
66 (repeat :tag "List"
26c9afc3
MB
67 (choice :format "%[Value Menu%] %v"
68 :value (file)
a1da1e37
MB
69 (cons :tag "Group parameter `mail-source'"
70 (const :format "" group))
26c9afc3
MB
71 (cons :tag "Spool file"
72 (const :format "" file)
73 (checklist :tag "Options" :greedy t
74 (group :inline t
75 (const :format "" :value :path)
76 file)))
77 (cons :tag "Several files in a directory"
78 (const :format "" directory)
79 (checklist :tag "Options" :greedy t
80 (group :inline t
81 (const :format "" :value :path)
82 (directory :tag "Path"))
83 (group :inline t
84 (const :format "" :value :suffix)
85 (string :tag "Suffix"))
86 (group :inline t
87 (const :format "" :value :predicate)
88 (function :tag "Predicate"))
89 (group :inline t
90 (const :format "" :value :prescript)
91 (choice :tag "Prescript"
92 :value nil
93 (string :format "%v")
94 (function :format "%v")))
95 (group :inline t
96 (const :format "" :value :postscript)
97 (choice :tag "Postscript"
98 :value nil
99 (string :format "%v")
100 (function :format "%v")))
101 (group :inline t
102 (const :format "" :value :plugged)
103 (boolean :tag "Plugged"))))
104 (cons :tag "POP3 server"
105 (const :format "" pop)
106 (checklist :tag "Options" :greedy t
107 (group :inline t
108 (const :format "" :value :server)
109 (string :tag "Server"))
110 (group :inline t
111 (const :format "" :value :port)
112 (choice :tag "Port"
113 :value "pop3"
01c52d31 114 (integer :format "%v")
26c9afc3
MB
115 (string :format "%v")))
116 (group :inline t
117 (const :format "" :value :user)
118 (string :tag "User"))
119 (group :inline t
120 (const :format "" :value :password)
121 (string :tag "Password"))
122 (group :inline t
123 (const :format "" :value :program)
124 (string :tag "Program"))
125 (group :inline t
126 (const :format "" :value :prescript)
127 (choice :tag "Prescript"
128 :value nil
129 (string :format "%v")
01c52d31
MB
130 (function :format "%v")
131 (const :tag "None" nil)))
26c9afc3
MB
132 (group :inline t
133 (const :format "" :value :postscript)
134 (choice :tag "Postscript"
135 :value nil
136 (string :format "%v")
01c52d31
MB
137 (function :format "%v")
138 (const :tag "None" nil)))
26c9afc3
MB
139 (group :inline t
140 (const :format "" :value :function)
141 (function :tag "Function"))
142 (group :inline t
143 (const :format ""
144 :value :authentication)
145 (choice :tag "Authentication"
146 :value apop
147 (const password)
148 (const apop)))
149 (group :inline t
150 (const :format "" :value :plugged)
01c52d31
MB
151 (boolean :tag "Plugged"))
152 (group :inline t
153 (const :format "" :value :stream)
154 (choice :tag "Stream"
155 :value nil
156 (const :tag "Clear" nil)
157 (const starttls)
a71e2379
G
158 (const :tag "SSL/TLS" ssl)))
159 (group :inline t
160 (const :format "" :value :leave)
161 (choice :format "\
162%{Leave mail on server%}:\n\t\t%[Value Menu%] %v"
163 :value nil
164 (const :tag "\
165Don't leave mails" nil)
166 (const :tag "\
167Leave all mails" t)
168 (number :tag "\
169Leave mails for this many days" :value 14)))))
26c9afc3
MB
170 (cons :tag "Maildir (qmail, postfix...)"
171 (const :format "" maildir)
172 (checklist :tag "Options" :greedy t
173 (group :inline t
174 (const :format "" :value :path)
175 (directory :tag "Path"))
176 (group :inline t
177 (const :format "" :value :plugged)
178 (boolean :tag "Plugged"))))
179 (cons :tag "IMAP server"
180 (const :format "" imap)
181 (checklist :tag "Options" :greedy t
182 (group :inline t
183 (const :format "" :value :server)
184 (string :tag "Server"))
185 (group :inline t
186 (const :format "" :value :port)
187 (choice :tag "Port"
188 :value 143
01c52d31 189 integer string))
26c9afc3
MB
190 (group :inline t
191 (const :format "" :value :user)
192 (string :tag "User"))
193 (group :inline t
194 (const :format "" :value :password)
195 (string :tag "Password"))
196 (group :inline t
197 (const :format "" :value :stream)
198 (choice :tag "Stream"
199 :value network
200 ,@mail-source-imap-streams))
201 (group :inline t
202 (const :format "" :value :program)
203 (string :tag "Program"))
204 (group :inline t
205 (const :format ""
206 :value :authenticator)
207 (choice :tag "Authenticator"
208 :value login
209 ,@mail-source-imap-authenticators))
210 (group :inline t
211 (const :format "" :value :mailbox)
212 (string :tag "Mailbox"
213 :value "INBOX"))
214 (group :inline t
215 (const :format "" :value :predicate)
216 (string :tag "Predicate"
217 :value "UNSEEN UNDELETED"))
218 (group :inline t
219 (const :format "" :value :fetchflag)
220 (string :tag "Fetchflag"
221 :value "\\Deleted"))
222 (group :inline t
223 (const :format ""
224 :value :dontexpunge)
225 (boolean :tag "Dontexpunge"))
26c9afc3
MB
226 (group :inline t
227 (const :format "" :value :plugged)
228 (boolean :tag "Plugged"))))))))
c113de23 229
23f87bed
MB
230(defcustom mail-source-ignore-errors nil
231 "*Ignore errors when querying mail sources.
232If nil, the user will be prompted when an error occurs. If non-nil,
a08b59c9 233the error will be ignored."
bf247b6e 234 :version "22.1"
a08b59c9
MB
235 :group 'mail-source
236 :type 'boolean)
23f87bed 237
c113de23
GM
238(defcustom mail-source-primary-source nil
239 "*Primary source for incoming mail.
240If non-nil, this maildrop will be checked periodically for new mail."
241 :group 'mail-source
242 :type 'sexp)
243
23f87bed
MB
244(defcustom mail-source-flash t
245 "*If non-nil, flash periodically when mail is available."
246 :group 'mail-source
247 :type 'boolean)
248
c113de23
GM
249(defcustom mail-source-crash-box "~/.emacs-mail-crash-box"
250 "File where mail will be stored while processing it."
251 :group 'mail-source
252 :type 'file)
253
23f87bed 254(defcustom mail-source-directory message-directory
531e5812 255 "Directory where incoming mail source files (if any) will be stored."
c113de23
GM
256 :group 'mail-source
257 :type 'directory)
258
259(defcustom mail-source-default-file-modes 384
260 "Set the mode bits of all new mail files to this integer."
261 :group 'mail-source
262 :type 'integer)
263
52bec650
MB
264(defcustom mail-source-delete-incoming
265 10 ;; development versions
266 ;; 2 ;; released versions
267 "If non-nil, delete incoming files after handling.
23f87bed 268If t, delete immediately, if nil, never delete. If a positive number, delete
52bec650
MB
269files older than number of days.
270
271Removing of old files happens in `mail-source-callback', i.e. no
272old incoming files will be deleted unless you receive new mail.
273You may also set this variable to nil and call
274`mail-source-delete-old-incoming' interactively."
23f87bed 275 :group 'mail-source
52bec650 276 :version "22.2" ;; No Gnus / Gnus 5.10.10 (default changed)
23f87bed
MB
277 :type '(choice (const :tag "immediately" t)
278 (const :tag "never" nil)
279 (integer :tag "days")))
280
37a68866
MB
281(defcustom mail-source-delete-old-incoming-confirm nil
282 "If non-nil, ask for confirmation before deleting old incoming files.
23f87bed
MB
283This variable only applies when `mail-source-delete-incoming' is a positive
284number."
37a68866 285 :version "22.2" ;; No Gnus / Gnus 5.10.10 (default changed)
c113de23
GM
286 :group 'mail-source
287 :type 'boolean)
288
289(defcustom mail-source-incoming-file-prefix "Incoming"
290 "Prefix for file name for storing incoming mail"
291 :group 'mail-source
292 :type 'string)
293
294(defcustom mail-source-report-new-mail-interval 5
295 "Interval in minutes between checks for new mail."
296 :group 'mail-source
297 :type 'number)
298
299(defcustom mail-source-idle-time-delay 5
300 "Number of idle seconds to wait before checking for new mail."
301 :group 'mail-source
302 :type 'number)
303
23f87bed
MB
304(defcustom mail-source-movemail-program nil
305 "If non-nil, name of program for fetching new mail."
bf247b6e 306 :version "22.1"
23f87bed
MB
307 :group 'mail-source
308 :type '(choice (const nil) string))
309
c113de23
GM
310;;; Internal variables.
311
312(defvar mail-source-string ""
313 "A dynamically bound string that says what the current mail source is.")
314
315(defvar mail-source-new-mail-available nil
316 "Flag indicating when new mail is available.")
317
318(eval-and-compile
319 (defvar mail-source-common-keyword-map
320 '((:plugged))
321 "Mapping from keywords to default values.
322Common keywords should be listed here.")
323
324 (defvar mail-source-keyword-map
325 '((file
326 (:prescript)
327 (:prescript-delay)
328 (:postscript)
329 (:path (or (getenv "MAIL")
4f926b3e 330 (expand-file-name (user-login-name) rmail-spool-directory))))
c113de23 331 (directory
cf92160d
SZ
332 (:prescript)
333 (:prescript-delay)
334 (:postscript)
c113de23
GM
335 (:path)
336 (:suffix ".spool")
337 (:predicate identity))
338 (pop
339 (:prescript)
340 (:prescript-delay)
341 (:postscript)
b8e0f0cd 342 ;; note server and port need to come before user and password
c113de23
GM
343 (:server (getenv "MAILHOST"))
344 (:port 110)
345 (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
346 (:program)
347 (:function)
348 (:password)
01c52d31 349 (:authentication password)
a71e2379
G
350 (:stream nil)
351 (:leave))
c113de23
GM
352 (maildir
353 (:path (or (getenv "MAILDIR") "~/Maildir/"))
23f87bed 354 (:subdirs ("cur" "new"))
c113de23
GM
355 (:function))
356 (imap
b8e0f0cd 357 ;; note server and port need to come before user and password
c113de23
GM
358 (:server (getenv "MAILHOST"))
359 (:port)
360 (:stream)
23f87bed 361 (:program)
c113de23
GM
362 (:authentication)
363 (:user (or (user-login-name) (getenv "LOGNAME") (getenv "USER")))
364 (:password)
365 (:mailbox "INBOX")
366 (:predicate "UNSEEN UNDELETED")
367 (:fetchflag "\\Deleted")
23f87bed
MB
368 (:prescript)
369 (:prescript-delay)
370 (:postscript)
6b958814 371 (:dontexpunge)))
c113de23
GM
372 "Mapping from keywords to default values.
373All keywords that can be used must be listed here."))
374
375(defvar mail-source-fetcher-alist
376 '((file mail-source-fetch-file)
377 (directory mail-source-fetch-directory)
378 (pop mail-source-fetch-pop)
379 (maildir mail-source-fetch-maildir)
6b958814 380 (imap mail-source-fetch-imap))
c113de23
GM
381 "A mapping from source type to fetcher function.")
382
383(defvar mail-source-password-cache nil)
384
385(defvar mail-source-plugged t)
386
387;;; Functions
388
389(eval-and-compile
390 (defun mail-source-strip-keyword (keyword)
391 "Strip the leading colon off the KEYWORD."
392 (intern (substring (symbol-name keyword) 1))))
393
58a67d68
MB
394;; generate a list of variable names paired with nil values
395;; suitable for usage in a `let' form
c113de23
GM
396(eval-and-compile
397 (defun mail-source-bind-1 (type)
398 (let* ((defaults (cdr (assq type mail-source-keyword-map)))
399 default bind)
400 (while (setq default (pop defaults))
401 (push (list (mail-source-strip-keyword (car default))
402 nil)
403 bind))
404 bind)))
405
406(defmacro mail-source-bind (type-source &rest body)
407 "Return a `let' form that binds all variables in source TYPE.
408TYPE-SOURCE is a list where the first element is the TYPE, and
409the second variable is the SOURCE.
410At run time, the mail source specifier SOURCE will be inspected,
411and the variables will be set according to it. Variables not
412specified will be given default values.
413
58a67d68
MB
414The user and password will be loaded from the auth-source values
415if those are available. They override the original user and
416password in a second `let' form.
417
c113de23 418After this is done, BODY will be executed in the scope
58a67d68 419of the second `let' form.
c113de23
GM
420
421The variables bound and their default values are described by
422the `mail-source-keyword-map' variable."
58a67d68 423 `(let* ,(mail-source-bind-1 (car type-source))
c113de23 424 (mail-source-set-1 ,(cadr type-source))
8336c962 425 ,@body))
c113de23
GM
426
427(put 'mail-source-bind 'lisp-indent-function 1)
23f87bed 428(put 'mail-source-bind 'edebug-form-spec '(sexp body))
c113de23
GM
429
430(defun mail-source-set-1 (source)
431 (let* ((type (pop source))
b8e0f0cd
G
432 (defaults (cdr (assq type mail-source-keyword-map)))
433 (search '(:max 1))
434 found default value keyword auth-info user-auth pass-auth)
435
436 ;; append to the search the useful info from the source and the defaults:
437 ;; user, host, and port
438
439 ;; the msname is the mail-source parameter
440 (dolist (msname '(:server :user :port))
441 ;; the asname is the auth-source parameter
442 (let* ((asname (case msname
443 (:server :host) ; auth-source uses :host
444 (t msname)))
445 ;; this is the mail-source default
446 (msdef1 (or (plist-get source msname)
447 (nth 1 (assoc msname defaults))))
448 ;; ...evaluated
449 (msdef (mail-source-value msdef1)))
450 (setq search (append (list asname
451 (if msdef msdef t))
452 search))))
453 ;; if the port is unknown yet, get it from the mail-source type
454 (unless (plist-get search :port)
455 (setq search (append (list :port (symbol-name type)))))
456
c113de23 457 (while (setq default (pop defaults))
58a67d68
MB
458 ;; for each default :SYMBOL, set SYMBOL to the plist value for :SYMBOL
459 ;; using `mail-source-value' to evaluate the plist value
c113de23 460 (set (mail-source-strip-keyword (setq keyword (car default)))
b8e0f0cd
G
461 ;; note the following reasons for this structure:
462 ;; 1) the auth-sources user and password override everything
463 ;; 2) it avoids macros, so it's cleaner
464 ;; 3) it falls through to the mail-sources and then default values
465 (cond
466 ((and
467 (eq keyword :user)
468 (setq user-auth (plist-get
469 ;; cache the search result in `found'
470 (or found
471 (setq found (nth 0 (apply 'auth-source-search
472 search))))
473 :user)))
474 user-auth)
475 ((and
476 (eq keyword :password)
477 (setq pass-auth (plist-get
478 ;; cache the search result in `found'
479 (or found
480 (setq found (nth 0 (apply 'auth-source-search
481 search))))
482 :secret)))
483 ;; maybe set the password to the return of the :secret function
484 (if (functionp pass-auth)
485 (setq pass-auth (funcall pass-auth))
486 pass-auth))
487 (t (if (setq value (plist-get source keyword))
488 (mail-source-value value)
489 (mail-source-value (cadr default)))))))))
c113de23
GM
490
491(eval-and-compile
492 (defun mail-source-bind-common-1 ()
493 (let* ((defaults mail-source-common-keyword-map)
494 default bind)
495 (while (setq default (pop defaults))
496 (push (list (mail-source-strip-keyword (car default))
497 nil)
498 bind))
499 bind)))
500
501(defun mail-source-set-common-1 (source)
502 (let* ((type (pop source))
503 (defaults mail-source-common-keyword-map)
504 (defaults-1 (cdr (assq type mail-source-keyword-map)))
505 default value keyword)
506 (while (setq default (pop defaults))
507 (set (mail-source-strip-keyword (setq keyword (car default)))
508 (if (setq value (plist-get source keyword))
509 (mail-source-value value)
510 (if (setq value (assq keyword defaults-1))
511 (mail-source-value (cadr value))
512 (mail-source-value (cadr default))))))))
513
514(defmacro mail-source-bind-common (source &rest body)
515 "Return a `let' form that binds all common variables.
516See `mail-source-bind'."
517 `(let ,(mail-source-bind-common-1)
518 (mail-source-set-common-1 source)
519 ,@body))
520
521(put 'mail-source-bind-common 'lisp-indent-function 1)
23f87bed 522(put 'mail-source-bind-common 'edebug-form-spec '(sexp body))
c113de23
GM
523
524(defun mail-source-value (value)
525 "Return the value of VALUE."
526 (cond
527 ;; String
528 ((stringp value)
529 value)
530 ;; Function
e66d8771 531 ((and (listp value) (symbolp (car value)) (fboundp (car value)))
c113de23
GM
532 (eval value))
533 ;; Just return the value.
534 (t
535 value)))
536
89cff466
GM
537(autoload 'nnheader-message "nnheader")
538
8c3e17f8 539(defun mail-source-fetch (source callback &optional method)
c113de23
GM
540 "Fetch mail from SOURCE and call CALLBACK zero or more times.
541CALLBACK will be called with the name of the file where (some of)
542the mail from SOURCE is put.
543Return the number of files that were found."
544 (mail-source-bind-common source
545 (if (or mail-source-plugged plugged)
546 (save-excursion
8695c9a7
LMI
547 ;; Special-case the `file' handler since it's so common and
548 ;; just adds noise.
549 (when (or (not (eq (car source) 'file))
550 (mail-source-bind (file source)
551 (file-exists-p path)))
552 (nnheader-message 4 "%sReading incoming mail from %s..."
553 (if method
554 (format "%s: " method)
555 "")
556 (car source)))
c113de23
GM
557 (let ((function (cadr (assq (car source) mail-source-fetcher-alist)))
558 (found 0))
559 (unless function
560 (error "%S is an invalid mail source specification" source))
561 ;; If there's anything in the crash box, we do it first.
562 (when (file-exists-p mail-source-crash-box)
563 (message "Processing mail from %s..." mail-source-crash-box)
564 (setq found (mail-source-callback
01c52d31
MB
565 callback mail-source-crash-box))
566 (mail-source-delete-crash-box))
c113de23 567 (+ found
23f87bed 568 (if (or debug-on-quit debug-on-error)
c113de23 569 (funcall function source callback)
23f87bed
MB
570 (condition-case err
571 (funcall function source callback)
572 (error
573 (if (and (not mail-source-ignore-errors)
574 (not
575 (yes-or-no-p
576 (format "Mail source %s error (%s). Continue? "
577 (if (memq ':password source)
578 (let ((s (copy-sequence source)))
bf247b6e 579 (setcar (cdr (memq ':password s))
23f87bed
MB
580 "********")
581 s)
582 source)
583 (cadr err)))))
584 (error "Cannot get new mail"))
585 0)))))))))
586
16d8cf52
GM
587(declare-function gnus-message "gnus-util" (level &rest args))
588
23f87bed
MB
589(defun mail-source-delete-old-incoming (&optional age confirm)
590 "Remove incoming files older than AGE days.
591If CONFIRM is non-nil, ask for confirmation before removing a file."
592 (interactive "P")
16d8cf52 593 (require 'gnus-util)
23f87bed
MB
594 (let* ((high2days (/ 65536.0 60 60 24));; convert high bits to days
595 (low2days (/ 1.0 65536.0)) ;; convert low bits to days
596 (diff (if (natnump age) age 30));; fallback, if no valid AGE given
597 currday files)
598 (setq files (directory-files
599 mail-source-directory t
9b3ebcb6
MB
600 (concat "\\`"
601 (regexp-quote mail-source-incoming-file-prefix)))
23f87bed
MB
602 currday (* (car (current-time)) high2days)
603 currday (+ currday (* low2days (nth 1 (current-time)))))
604 (while files
605 (let* ((ffile (car files))
606 (bfile (gnus-replace-in-string
607 ffile "\\`.*/\\([^/]+\\)\\'" "\\1"))
608 (filetime (nth 5 (file-attributes ffile)))
609 (fileday (* (car filetime) high2days))
610 (fileday (+ fileday (* low2days (nth 1 filetime)))))
611 (setq files (cdr files))
612 (when (and (> (- currday fileday) diff)
37a68866
MB
613 (if confirm
614 (y-or-n-p
615 (format "\
616Delete old (> %s day(s)) incoming mail file `%s'? " diff bfile))
617 (gnus-message 8 "\
618Deleting old (> %s day(s)) incoming mail file `%s'." diff bfile)
619 t))
23f87bed 620 (delete-file ffile))))))
c113de23
GM
621
622(defun mail-source-callback (callback info)
01c52d31 623 "Call CALLBACK on the mail file. Pass INFO on to CALLBACK."
c113de23
GM
624 (if (or (not (file-exists-p mail-source-crash-box))
625 (zerop (nth 7 (file-attributes mail-source-crash-box))))
626 (progn
627 (when (file-exists-p mail-source-crash-box)
628 (delete-file mail-source-crash-box))
629 0)
01c52d31
MB
630 (funcall callback mail-source-crash-box info)))
631
89cff466
GM
632(autoload 'gnus-float-time "gnus-util")
633
a2bb410e
LMI
634(defvar mail-source-incoming-last-checked-time nil)
635
01c52d31
MB
636(defun mail-source-delete-crash-box ()
637 (when (file-exists-p mail-source-crash-box)
638 ;; Delete or move the incoming mail out of the way.
639 (if (eq mail-source-delete-incoming t)
640 (delete-file mail-source-crash-box)
85816ac1
LMI
641 (let ((incoming
642 (mm-make-temp-file
643 (expand-file-name
644 mail-source-incoming-file-prefix
645 mail-source-directory))))
646 (unless (file-exists-p (file-name-directory incoming))
647 (make-directory (file-name-directory incoming) t))
648 (rename-file mail-source-crash-box incoming t)
649 ;; remove old incoming files?
650 (when (natnump mail-source-delete-incoming)
651 ;; Don't check for old incoming files more than once per day to
652 ;; save a lot of file accesses.
653 (when (or (null mail-source-incoming-last-checked-time)
89cff466 654 (> (gnus-float-time
85816ac1
LMI
655 (time-since mail-source-incoming-last-checked-time))
656 (* 24 60 60)))
657 (setq mail-source-incoming-last-checked-time (current-time))
530b8957
LMI
658 (mail-source-delete-old-incoming
659 mail-source-delete-incoming
660 mail-source-delete-old-incoming-confirm)))))))
c113de23
GM
661
662(defun mail-source-movemail (from to)
663 "Move FROM to TO using movemail."
664 (if (not (file-writable-p to))
665 (error "Can't write to crash box %s. Not moving mail" to)
666 (let ((to (file-truename (expand-file-name to)))
667 errors result)
668 (setq to (file-truename to)
669 from (file-truename from))
670 ;; Set TO if have not already done so, and rename or copy
671 ;; the file FROM to TO if and as appropriate.
672 (cond
673 ((file-exists-p to)
674 ;; The crash box exists already.
675 t)
676 ((not (file-exists-p from))
677 ;; There is no inbox.
678 (setq to nil))
679 ((zerop (nth 7 (file-attributes from)))
680 ;; Empty file.
681 (setq to nil))
682 (t
683 ;; If getting from mail spool directory, use movemail to move
684 ;; rather than just renaming, so as to interlock with the
685 ;; mailer.
686 (unwind-protect
687 (save-excursion
688 (setq errors (generate-new-buffer " *mail source loss*"))
689 (let ((default-directory "/"))
690 (setq result
691 (apply
692 'call-process
693 (append
694 (list
23f87bed
MB
695 (or mail-source-movemail-program
696 (expand-file-name "movemail" exec-directory))
c113de23
GM
697 nil errors nil from to)))))
698 (when (file-exists-p to)
699 (set-file-modes to mail-source-default-file-modes))
23f87bed
MB
700 (if (and (or (not (buffer-modified-p errors))
701 (zerop (buffer-size errors)))
702 (and (numberp result)
703 (zerop result)))
c113de23
GM
704 ;; No output => movemail won.
705 t
706 (set-buffer errors)
707 ;; There may be a warning about older revisions. We
708 ;; ignore that.
709 (goto-char (point-min))
710 (if (search-forward "older revision" nil t)
711 t
712 ;; Probably a real error.
713 (subst-char-in-region (point-min) (point-max) ?\n ?\ )
714 (goto-char (point-max))
715 (skip-chars-backward " \t")
716 (delete-region (point) (point-max))
717 (goto-char (point-min))
718 (when (looking-at "movemail: ")
719 (delete-region (point-min) (match-end 0)))
23f87bed 720 ;; Result may be a signal description string.
c113de23 721 (unless (yes-or-no-p
23f87bed 722 (format "movemail: %s (%s return). Continue? "
c113de23
GM
723 (buffer-string) result))
724 (error "%s" (buffer-string)))
725 (setq to nil)))))))
726 (when (and errors
727 (buffer-name errors))
728 (kill-buffer errors))
729 ;; Return whether we moved successfully or not.
730 to)))
731
c113de23 732(defun mail-source-fetch-with-program (program)
23f87bed
MB
733 (eq 0 (call-process shell-file-name nil nil nil
734 shell-command-switch program)))
c113de23
GM
735
736(defun mail-source-run-script (script spec &optional delay)
737 (when script
23f87bed 738 (if (functionp script)
c113de23
GM
739 (funcall script)
740 (mail-source-call-script
741 (format-spec script spec))))
742 (when delay
743 (sleep-for delay)))
744
745(defun mail-source-call-script (script)
01c52d31
MB
746 (let ((background nil)
747 (stderr (get-buffer-create " *mail-source-stderr*"))
748 result)
c113de23
GM
749 (when (string-match "& *$" script)
750 (setq script (substring script 0 (match-beginning 0))
751 background 0))
01c52d31
MB
752 (setq result
753 (call-process shell-file-name nil background nil
754 shell-command-switch script))
755 (when (and result
756 (not (zerop result)))
757 (set-buffer stderr)
758 (message "Mail source error: %s" (buffer-string)))
759 (kill-buffer stderr)))
c113de23
GM
760
761;;;
762;;; Different fetchers
763;;;
764
765(defun mail-source-fetch-file (source callback)
766 "Fetcher for single-file sources."
767 (mail-source-bind (file source)
768 (mail-source-run-script
769 prescript (format-spec-make ?t mail-source-crash-box)
770 prescript-delay)
771 (let ((mail-source-string (format "file:%s" path)))
772 (if (mail-source-movemail path mail-source-crash-box)
773 (prog1
774 (mail-source-callback callback path)
775 (mail-source-run-script
01c52d31
MB
776 postscript (format-spec-make ?t mail-source-crash-box))
777 (mail-source-delete-crash-box))
c113de23
GM
778 0))))
779
780(defun mail-source-fetch-directory (source callback)
781 "Fetcher for directory sources."
782 (mail-source-bind (directory source)
35037882 783 (mail-source-run-script
23f87bed 784 prescript (format-spec-make ?t path) prescript-delay)
c113de23
GM
785 (let ((found 0)
786 (mail-source-string (format "directory:%s" path)))
787 (dolist (file (directory-files
788 path t (concat (regexp-quote suffix) "$")))
789 (when (and (file-regular-p file)
790 (funcall predicate file)
791 (mail-source-movemail file mail-source-crash-box))
01c52d31
MB
792 (incf found (mail-source-callback callback file))
793 (mail-source-run-script postscript (format-spec-make ?t path))
794 (mail-source-delete-crash-box)))
c113de23
GM
795 found)))
796
797(defun mail-source-fetch-pop (source callback)
798 "Fetcher for single-file sources."
799 (mail-source-bind (pop source)
01c52d31 800 ;; fixme: deal with stream type in format specs
c113de23
GM
801 (mail-source-run-script
802 prescript
803 (format-spec-make ?p password ?t mail-source-crash-box
804 ?s server ?P port ?u user)
805 prescript-delay)
806 (let ((from (format "%s:%s:%s" server user port))
807 (mail-source-string (format "pop:%s@%s" user server))
03fdf5ca 808 (process-environment (if server
57829a28
KY
809 (cons (concat "MAILHOST=" server)
810 process-environment)
03fdf5ca 811 process-environment))
c113de23
GM
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 818 (format "Password for %s at %s: " user server)))))
c113de23
GM
819 (setq result
820 (cond
821 (program
822 (mail-source-fetch-with-program
823 (format-spec
824 program
825 (format-spec-make ?p password ?t mail-source-crash-box
826 ?s server ?P port ?u user))))
827 (function
828 (funcall function mail-source-crash-box))
829 ;; The default is to use pop3.el.
830 (t
292f71fe 831 (require 'pop3)
c113de23
GM
832 (let ((pop3-password password)
833 (pop3-maildrop user)
834 (pop3-mailhost server)
835 (pop3-port port)
836 (pop3-authentication-scheme
01c52d31 837 (if (eq authentication 'apop) 'apop 'pass))
a71e2379
G
838 (pop3-stream-type stream)
839 (pop3-leave-mail-on-server leave))
23f87bed 840 (if (or debug-on-quit debug-on-error)
e574f629 841 (save-excursion (pop3-movemail mail-source-crash-box))
23f87bed 842 (condition-case err
e574f629 843 (save-excursion (pop3-movemail mail-source-crash-box))
23f87bed
MB
844 (error
845 ;; We nix out the password in case the error
846 ;; was because of a wrong password being given.
847 (setq mail-source-password-cache
848 (delq (assoc from mail-source-password-cache)
849 mail-source-password-cache))
850 (signal (car err) (cdr err)))))))))
c113de23
GM
851 (if result
852 (progn
853 (when (eq authentication 'password)
854 (unless (assoc from mail-source-password-cache)
855 (push (cons from password) mail-source-password-cache)))
856 (prog1
857 (mail-source-callback callback server)
858 ;; Update display-time's mail flag, if relevant.
859 (if (equal source mail-source-primary-source)
860 (setq mail-source-new-mail-available nil))
861 (mail-source-run-script
862 postscript
863 (format-spec-make ?p password ?t mail-source-crash-box
01c52d31
MB
864 ?s server ?P port ?u user))
865 (mail-source-delete-crash-box)))
c113de23
GM
866 ;; We nix out the password in case the error
867 ;; was because of a wrong password being given.
868 (setq mail-source-password-cache
869 (delq (assoc from mail-source-password-cache)
870 mail-source-password-cache))
871 0))))
872
873(defun mail-source-check-pop (source)
874 "Check whether there is new mail."
875 (mail-source-bind (pop source)
876 (let ((from (format "%s:%s:%s" server user port))
877 (mail-source-string (format "pop:%s@%s" user server))
03fdf5ca 878 (process-environment (if server
57829a28
KY
879 (cons (concat "MAILHOST=" server)
880 process-environment)
03fdf5ca 881 process-environment))
c113de23
GM
882 result)
883 (when (eq authentication 'password)
884 (setq password
885 (or password
886 (cdr (assoc from mail-source-password-cache))
23f87bed 887 (read-passwd
c113de23
GM
888 (format "Password for %s at %s: " user server))))
889 (unless (assoc from mail-source-password-cache)
890 (push (cons from password) mail-source-password-cache)))
c113de23
GM
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