Regenerate ldefs-boot.el
[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
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))
03fdf5ca 812 (process-environment (if server
57829a28
KY
813 (cons (concat "MAILHOST=" server)
814 process-environment)
03fdf5ca 815 process-environment))
c113de23
GM
816 result)
817 (when (eq authentication 'password)
818 (setq password
819 (or password
820 (cdr (assoc from mail-source-password-cache))
23f87bed 821 (read-passwd
c113de23 822 (format "Password for %s at %s: " user server)))))
c113de23
GM
823 (setq result
824 (cond
825 (program
826 (mail-source-fetch-with-program
827 (format-spec
828 program
829 (format-spec-make ?p password ?t mail-source-crash-box
830 ?s server ?P port ?u user))))
831 (function
832 (funcall function mail-source-crash-box))
833 ;; The default is to use pop3.el.
834 (t
292f71fe 835 (require 'pop3)
c113de23
GM
836 (let ((pop3-password password)
837 (pop3-maildrop user)
838 (pop3-mailhost server)
839 (pop3-port port)
840 (pop3-authentication-scheme
01c52d31 841 (if (eq authentication 'apop) 'apop 'pass))
a71e2379
G
842 (pop3-stream-type stream)
843 (pop3-leave-mail-on-server leave))
23f87bed 844 (if (or debug-on-quit debug-on-error)
e574f629 845 (save-excursion (pop3-movemail mail-source-crash-box))
23f87bed 846 (condition-case err
e574f629 847 (save-excursion (pop3-movemail mail-source-crash-box))
23f87bed
MB
848 (error
849 ;; We nix out the password in case the error
850 ;; was because of a wrong password being given.
851 (setq mail-source-password-cache
852 (delq (assoc from mail-source-password-cache)
853 mail-source-password-cache))
854 (signal (car err) (cdr err)))))))))
c113de23
GM
855 (if result
856 (progn
857 (when (eq authentication 'password)
858 (unless (assoc from mail-source-password-cache)
859 (push (cons from password) mail-source-password-cache)))
860 (prog1
861 (mail-source-callback callback server)
862 ;; Update display-time's mail flag, if relevant.
863 (if (equal source mail-source-primary-source)
864 (setq mail-source-new-mail-available nil))
865 (mail-source-run-script
866 postscript
867 (format-spec-make ?p password ?t mail-source-crash-box
01c52d31
MB
868 ?s server ?P port ?u user))
869 (mail-source-delete-crash-box)))
c113de23
GM
870 ;; We nix out the password in case the error
871 ;; was because of a wrong password being given.
872 (setq mail-source-password-cache
873 (delq (assoc from mail-source-password-cache)
874 mail-source-password-cache))
875 0))))
876
877(defun mail-source-check-pop (source)
878 "Check whether there is new mail."
879 (mail-source-bind (pop source)
880 (let ((from (format "%s:%s:%s" server user port))
881 (mail-source-string (format "pop:%s@%s" user server))
03fdf5ca 882 (process-environment (if server
57829a28
KY
883 (cons (concat "MAILHOST=" server)
884 process-environment)
03fdf5ca 885 process-environment))
c113de23
GM
886 result)
887 (when (eq authentication 'password)
888 (setq password
889 (or password
890 (cdr (assoc from mail-source-password-cache))
23f87bed 891 (read-passwd
c113de23
GM
892 (format "Password for %s at %s: " user server))))
893 (unless (assoc from mail-source-password-cache)
894 (push (cons from password) mail-source-password-cache)))
c113de23
GM
895 (setq result
896 (cond
897 ;; No easy way to check whether mail is waiting for these.
898 (program)
899 (function)
900 ;; The default is to use pop3.el.
901 (t
292f71fe 902 (require 'pop3)
c113de23
GM
903 (let ((pop3-password password)
904 (pop3-maildrop user)
905 (pop3-mailhost server)
906 (pop3-port port)
907 (pop3-authentication-scheme
908 (if (eq authentication 'apop) 'apop 'pass)))
23f87bed
MB
909 (if (or debug-on-quit debug-on-error)
910 (save-excursion (pop3-get-message-count))
911 (condition-case err
912 (save-excursion (pop3-get-message-count))
913 (error
914 ;; We nix out the password in case the error
915 ;; was because of a wrong password being given.
916 (setq mail-source-password-cache
917 (delq (assoc from mail-source-password-cache)
918 mail-source-password-cache))
919 (signal (car err) (cdr err)))))))))
c113de23
GM
920 (if result
921 ;; Inform display-time that we have new mail.
922 (setq mail-source-new-mail-available (> result 0))
923 ;; We nix out the password in case the error
924 ;; was because of a wrong password being given.
925 (setq mail-source-password-cache
926 (delq (assoc from mail-source-password-cache)
927 mail-source-password-cache)))
928 result)))
929
23f87bed
MB
930(defun mail-source-touch-pop ()
931 "Open and close a POP connection shortly.
932POP server should be defined in `mail-source-primary-source' (which is
933preferred) or `mail-sources'. You may use it for the POP-before-SMTP
934authentication. To do that, you need to set the
935`message-send-mail-function' variable as `message-smtpmail-send-it'
936and put the following line in your ~/.gnus.el file:
937
938\(add-hook 'message-send-mail-hook 'mail-source-touch-pop)
939
940See the Gnus manual for details."
941 (let ((sources (if mail-source-primary-source
942 (list mail-source-primary-source)
943 mail-sources)))
944 (while sources
945 (if (eq 'pop (car (car sources)))
946 (mail-source-check-pop (car sources)))
947 (setq sources (cdr sources)))))
948
c113de23
GM
949(defun mail-source-new-mail-p ()
950 "Handler for `display-time' to indicate when new mail is available."
23f87bed
MB
951 ;; Flash (ie. ring the visible bell) if mail is available.
952 (if (and mail-source-flash mail-source-new-mail-available)
953 (let ((visible-bell t))
954 (ding)))
c113de23
GM
955 ;; Only report flag setting; flag is updated on a different schedule.
956 mail-source-new-mail-available)
957
958
959(defvar mail-source-report-new-mail nil)
960(defvar mail-source-report-new-mail-timer nil)
961(defvar mail-source-report-new-mail-idle-timer nil)
962
c113de23
GM
963(defun mail-source-start-idle-timer ()
964 ;; Start our idle timer if necessary, so we delay the check until the
965 ;; user isn't typing.
966 (unless mail-source-report-new-mail-idle-timer
967 (setq mail-source-report-new-mail-idle-timer
968 (run-with-idle-timer
969 mail-source-idle-time-delay
970 nil
971 (lambda ()
23f87bed
MB
972 (unwind-protect
973 (mail-source-check-pop mail-source-primary-source)
974 (setq mail-source-report-new-mail-idle-timer nil)))))
c113de23
GM
975 ;; Since idle timers created when Emacs is already in the idle
976 ;; state don't get activated until Emacs _next_ becomes idle, we
977 ;; need to force our timer to be considered active now. We do
978 ;; this by being naughty and poking the timer internals directly
979 ;; (element 0 of the vector is nil if the timer is active).
980 (aset mail-source-report-new-mail-idle-timer 0 nil)))
981
982(defun mail-source-report-new-mail (arg)
983 "Toggle whether to report when new mail is available.
984This only works when `display-time' is enabled."
985 (interactive "P")
986 (if (not mail-source-primary-source)
715a2ca2 987 (error "Need to set `mail-source-primary-source' to check for new mail"))
c113de23
GM
988 (let ((on (if (null arg)
989 (not mail-source-report-new-mail)
990 (> (prefix-numeric-value arg) 0))))
991 (setq mail-source-report-new-mail on)
992 (and mail-source-report-new-mail-timer
72fc0418 993 (nnheader-cancel-timer mail-source-report-new-mail-timer))
c113de23 994 (and mail-source-report-new-mail-idle-timer
72fc0418 995 (nnheader-cancel-timer mail-source-report-new-mail-idle-timer))
c113de23
GM
996 (setq mail-source-report-new-mail-timer nil)
997 (setq mail-source-report-new-mail-idle-timer nil)
998 (if on
999 (progn
1000 (require 'time)
5b5dafd2 1001 ;; display-time-mail-function is an Emacs feature.
c113de23
GM
1002 (setq display-time-mail-function #'mail-source-new-mail-p)
1003 ;; Set up the main timer.
1004 (setq mail-source-report-new-mail-timer
01c52d31 1005 (run-at-time
23f87bed
MB
1006 (* 60 mail-source-report-new-mail-interval)
1007 (* 60 mail-source-report-new-mail-interval)
1008 #'mail-source-start-idle-timer))
c113de23
GM
1009 ;; When you get new mail, clear "Mail" from the mode line.
1010 (add-hook 'nnmail-post-get-new-mail-hook
1011 'display-time-event-handler)
1012 (message "Mail check enabled"))
1013 (setq display-time-mail-function nil)
1014 (remove-hook 'nnmail-post-get-new-mail-hook
1015 'display-time-event-handler)
1016 (message "Mail check disabled"))))
1017
1018(defun mail-source-fetch-maildir (source callback)
1019 "Fetcher for maildir sources."
1020 (mail-source-bind (maildir source)
1021 (let ((found 0)
1022 mail-source-string)
1023 (unless (string-match "/$" path)
1024 (setq path (concat path "/")))
1025 (dolist (subdir subdirs)
1026 (when (file-directory-p (concat path subdir))
1027 (setq mail-source-string (format "maildir:%s%s" path subdir))
1028 (dolist (file (directory-files (concat path subdir) t))
1029 (when (and (not (file-directory-p file))
1030 (not (if function
29291ef1 1031 ;; `function' should return nil if successful.
c113de23 1032 (funcall function file mail-source-crash-box)
a1506d29 1033 (let ((coding-system-for-write
c113de23 1034 mm-text-coding-system)
a1506d29 1035 (coding-system-for-read
c113de23
GM
1036 mm-text-coding-system))
1037 (with-temp-file mail-source-crash-box
1038 (insert-file-contents file)
1039 (goto-char (point-min))
23f87bed
MB
1040;;; ;; Unix mail format
1041;;; (unless (looking-at "\n*From ")
1042;;; (insert "From maildir "
1043;;; (current-time-string) "\n"))
1044;;; (while (re-search-forward "^From " nil t)
1045;;; (replace-match ">From "))
1046;;; (goto-char (point-max))
126cbb42 1047;;; (insert "\n\n")
c113de23 1048 ;; MMDF mail format
126cbb42 1049 (insert "\001\001\001\001\n"))
29291ef1
A
1050 (delete-file file)
1051 nil))))
01c52d31
MB
1052 (incf found (mail-source-callback callback file))
1053 (mail-source-delete-crash-box)))))
c113de23
GM
1054 found)))
1055
8abf1b22
GM
1056(autoload 'imap-open "imap")
1057(autoload 'imap-authenticate "imap")
1058(autoload 'imap-mailbox-select "imap")
1059(autoload 'imap-mailbox-unselect "imap")
1060(autoload 'imap-mailbox-close "imap")
1061(autoload 'imap-search "imap")
1062(autoload 'imap-fetch "imap")
1063(autoload 'imap-close "imap")
1064(autoload 'imap-error-text "imap")
1065(autoload 'imap-message-flags-add "imap")
1066(autoload 'imap-list-to-message-set "imap")
1067(autoload 'imap-range-to-message-set "imap")
1068(autoload 'nnheader-ms-strip-cr "nnheader")
c113de23 1069
1ffeb586
GM
1070(autoload 'gnus-compress-sequence "gnus-range")
1071
72fc0418
DL
1072(defvar mail-source-imap-file-coding-system 'binary
1073 "Coding system for the crashbox made by `mail-source-fetch-imap'.")
1074
1ffeb586
GM
1075;; Autoloads will bring in imap before this is called.
1076(declare-function imap-capability "imap" (&optional identifier buffer))
1077
c113de23
GM
1078(defun mail-source-fetch-imap (source callback)
1079 "Fetcher for imap sources."
1080 (mail-source-bind (imap source)
23f87bed
MB
1081 (mail-source-run-script
1082 prescript (format-spec-make ?p password ?t mail-source-crash-box
1083 ?s server ?P port ?u user)
1084 prescript-delay)
c113de23
GM
1085 (let ((from (format "%s:%s:%s" server user port))
1086 (found 0)
23f87bed 1087 (buf (generate-new-buffer " *imap source*"))
c113de23 1088 (mail-source-string (format "imap:%s:%s" server mailbox))
23f87bed 1089 (imap-shell-program (or (list program) imap-shell-program))
c113de23
GM
1090 remove)
1091 (if (and (imap-open server port stream authentication buf)
1092 (imap-authenticate
1093 user (or (cdr (assoc from mail-source-password-cache))
1094 password) buf)
1095 (imap-mailbox-select mailbox nil buf))
4f926b3e 1096 (let ((coding-system-for-write mail-source-imap-file-coding-system)
126cbb42 1097 str)
c113de23 1098 (with-temp-file mail-source-crash-box
4f926b3e
DL
1099 ;; Avoid converting 8-bit chars from inserted strings to
1100 ;; multibyte.
1101 (mm-disable-multibyte)
c113de23
GM
1102 ;; remember password
1103 (with-current-buffer buf
23f87bed
MB
1104 (when (and imap-password
1105 (not (assoc from mail-source-password-cache)))
c113de23
GM
1106 (push (cons from imap-password) mail-source-password-cache)))
1107 ;; if predicate is nil, use all uids
1108 (dolist (uid (imap-search (or predicate "1:*") buf))
23f87bed
MB
1109 (when (setq str
1110 (if (imap-capability 'IMAP4rev1 buf)
1111 (caddar (imap-fetch uid "BODY.PEEK[]"
1112 'BODYDETAIL nil buf))
1113 (imap-fetch uid "RFC822.PEEK" 'RFC822 nil buf)))
c113de23
GM
1114 (push uid remove)
1115 (insert "From imap " (current-time-string) "\n")
1116 (save-excursion
1117 (insert str "\n\n"))
01c52d31
MB
1118 (while (let ((case-fold-search nil))
1119 (re-search-forward "^From " nil t))
c113de23
GM
1120 (replace-match ">From "))
1121 (goto-char (point-max))))
1122 (nnheader-ms-strip-cr))
1123 (incf found (mail-source-callback callback server))
01c52d31 1124 (mail-source-delete-crash-box)
c113de23 1125 (when (and remove fetchflag)
23f87bed 1126 (setq remove (nreverse remove))
c113de23 1127 (imap-message-flags-add
72fc0418
DL
1128 (imap-range-to-message-set (gnus-compress-sequence remove))
1129 fetchflag nil buf))
c113de23
GM
1130 (if dontexpunge
1131 (imap-mailbox-unselect buf)
23f87bed 1132 (imap-mailbox-close nil buf))
c113de23
GM
1133 (imap-close buf))
1134 (imap-close buf)
1135 ;; We nix out the password in case the error
1136 ;; was because of a wrong password being given.
1137 (setq mail-source-password-cache
1138 (delq (assoc from mail-source-password-cache)
1139 mail-source-password-cache))
23f87bed 1140 (error "IMAP error: %s" (imap-error-text buf)))
c113de23 1141 (kill-buffer buf)
23f87bed
MB
1142 (mail-source-run-script
1143 postscript
1144 (format-spec-make ?p password ?t mail-source-crash-box
1145 ?s server ?P port ?u user))
c113de23
GM
1146 found)))
1147
c113de23
GM
1148(provide 'mail-source)
1149
1150;;; mail-source.el ends here