Synch to No Gnus 200410142233.
[elisp/gnus.git-] / lisp / pop3.el
1 ;;; pop3.el --- Post Office Protocol (RFC 1460) interface
2
3 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004
4 ;;        Free Software Foundation, Inc.
5
6 ;; Author: Richard L. Pieri <ratinox@peorth.gweep.net>
7 ;;      Daiki Ueno  <ueno@ueda.info.waseda.ac.jp>
8 ;;      Katsumi Yamaoka <yamaoka@jpl.org>
9 ;; Maintainer: Volunteers
10 ;; Keywords: mail
11
12 ;; This file is part of T-gnus.
13
14 ;; GNU Emacs is free software; you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; any later version.
18
19 ;; GNU Emacs is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 ;; GNU General Public License for more details.
23
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
26 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
27 ;; Boston, MA 02111-1307, USA.
28
29 ;;; Commentary:
30
31 ;; Most of the standard Post Office Protocol version 3 (RFC 1460) commands
32 ;; are implemented.  The LIST command has not been implemented due to lack
33 ;; of actual usefulness.
34 ;; The optional POP3 command TOP has not been implemented.
35
36 ;; This program was inspired by Kyle E. Jones's vm-pop program.
37
38 ;;; Gnus:
39
40 ;; You can use this program for Gnus, without needing any modification.
41 ;; There are two ways to do that; one is to replace Gnus' pop3.el with
42 ;; it when installing Gnus; the other is to replace Gnus' pop3.el which
43 ;; has been installed with this module and byte-compile it.
44
45 ;; Note: you should not modify the value for the `pop' section of the
46 ;; `mail-source-keyword-map' variable.
47
48 ;; This program provides the following features in addition to Gnus:
49
50 ;; 1. You can use SSL or STARTTLS stream to connect to mail servers.
51 ;;    For example, specify the `:connection' keyword and the value pair
52 ;;    in a mail-source as follows:
53 ;;
54 ;;(setq mail-sources '((pop :server "pop3.mail.server" :port 995
55 ;;                        :connection ssl :authentication apop)))
56 ;;
57 ;;    For STARTTLS stream, use `tls' isntead of `ssl'.  The default
58 ;;    connection type is defined by `pop3-connection-type' which
59 ;;    defaults to nil.
60
61 ;; 2. You can fetch mails without deleting them in mail servers.  To do
62 ;;    that, specify the `:leave' keyword with the value t as follows:
63 ;;
64 ;;(setq mail-sources '((pop :server "pop3.mail.server" :leave t)))
65 ;;
66 ;;    Already read mails are registered into the ~/.uidls-SERVER file
67 ;;    (which is the default, see `pop3-uidl-file-name'), and you will
68 ;;    never need to fetch them twice.  The default value for the
69 ;;    `:leave' keyword is specified by the `pop3-leave-mail-on-server'
70 ;;    variable.  You have no need to modify that value normally.
71
72 ;; 3. See the source code for some other miscellaneous extended features.
73
74 ;;; Code:
75
76 (eval-when-compile
77   (require 'cl))
78
79 (require 'mail-utils)
80
81 (defgroup pop3 nil
82   "Post Office Protocol"
83   :group 'mail
84   :group 'mail-source)
85
86 (defcustom pop3-maildrop (or (user-login-name)
87                              (getenv "LOGNAME")
88                              (getenv "USER"))
89   "*POP3 maildrop."
90   :version "21.4" ;; Oort Gnus
91   :type 'string
92   :group 'pop3)
93
94 (defcustom pop3-mailhost (or (getenv "MAILHOST") ;; nil -> mismatch
95                              "pop3")
96   "*POP3 mailhost."
97   :version "21.4" ;; Oort Gnus
98   :type 'string
99   :group 'pop3)
100
101 (defcustom pop3-port 110
102   "*POP3 port."
103   :version "21.4" ;; Oort Gnus
104   :type 'number
105   :group 'pop3)
106
107 (defcustom pop3-connection-type nil
108   "*POP3 connection type."
109   :type '(choice (const :tag "Not specified" nil)
110                  (const tls)
111                  (const ssl))
112   :group 'pop3)
113
114 (defcustom pop3-password-required t
115   "*Non-nil if a password is required when connecting to POP server."
116   :version "21.4" ;; Oort Gnus
117   :type 'boolean
118   :group 'pop3)
119
120 ;; Should this be customizable?
121 (defvar pop3-password nil
122   "*Password to use when connecting to POP server.")
123
124 (defcustom pop3-authentication-scheme 'pass
125   "*POP3 authentication scheme.
126 Defaults to 'pass, for the standard USER/PASS authentication.  Other valid
127 values are 'apop."
128   :version "21.4" ;; Oort Gnus
129   :type '(choice (const :tag "USER/PASS" pass)
130                  (const :tag "APOP" apop))
131   :group 'pop3)
132
133 (defcustom pop3-leave-mail-on-server nil
134   "*Non-nil if the mail is to be left on the POP server after fetching."
135   :version "21.4" ;; Oort Gnus
136   :type 'boolean
137   :group 'pop3)
138
139 (defvar pop3-timestamp nil
140   "Timestamp returned when initially connected to the POP server.
141 Used for APOP authentication.")
142
143 (defcustom pop3-maximum-message-size nil
144   "If non-nil only download messages smaller than this."
145   :type '(choice (const :tag "Unlimited" nil)
146                  (integer :tag "Maximum size" :format "%t: %v\n" :size 0))
147   :group 'pop3)
148
149 (defcustom pop3-except-header-regexp nil
150   "If non-nil we do not retrieve messages whose headers are matching this regexp."
151   :type '(choice (const :tag "Retrieve any messages" nil)
152                  (regexp :format "%t: %v\n" :size 0))
153   :group 'pop3)
154
155 (defcustom pop3-uidl-file-name "~/.uidls"
156   "File in which to store the UIDL of processed messages."
157   :type '(file :format "%t: %v\n" :size 0)
158   :group 'pop3)
159
160 (defvar pop3-uidl-support nil
161   "Alist of servers and flags of whether they support UIDLs.
162 Users don't have to set this value.")
163
164 (defvar pop3-uidl-obarray (make-vector 31 0)
165   "Uidl hash table.")
166
167 (defvar pop3-read-point nil)
168 (defvar pop3-debug nil)
169
170 (eval-and-compile
171   (autoload 'starttls-open-stream "starttls")
172   (autoload 'starttls-negotiate "starttls"))
173
174 (defcustom pop3-ssl-program-name
175   (if (executable-find "openssl")
176       "openssl"
177     "ssleay")
178   "The program to run in a subprocess to open an SSL connection."
179   :type '(string :format "%t: %v\n" :size 0)
180   :group 'pop3)
181
182 (defcustom pop3-ssl-program-arguments
183   '("s_client" "-quiet")
184   "Arguments to be passed to the program `pop3-ssl-program-name'."
185   :type '(repeat (string :format "%v\n" :size 0))
186   :group 'pop3)
187
188 (defun pop3-progress-message (format percent &rest args)
189   (apply (function message) format args))
190
191 ;; Borrowed from nnheader-accept-process-output in nnheader.el.
192 (defvar pop3-read-timeout
193   (if (string-match "windows-nt\\|os/2\\|emx\\|cygwin"
194                     (symbol-name system-type))
195       ;; http://thread.gmane.org/v9655t3pjo.fsf@marauder.physik.uni-ulm.de
196       ;;
197       ;; IIRC, values lower than 1.0 didn't/don't work on Windows/DOS.
198       ;;
199       ;; There should probably be a runtime test to determine the timing
200       ;; resolution, or a primitive to report it.  I don't know off-hand
201       ;; what's possible.  Perhaps better, maybe the Windows/DOS primitive
202       ;; could round up non-zero timeouts to a minimum of 1.0?
203       1.0
204     0.1)
205   "How long pop3 should wait between checking for the end of output.
206 Shorter values mean quicker response, but are more CPU intensive.")
207
208 ;; Borrowed from nnheader-accept-process-output in nnheader.el.
209 (defun pop3-accept-process-output (process)
210   (accept-process-output
211    process
212    (truncate pop3-read-timeout)
213    (truncate (* (- pop3-read-timeout
214                    (truncate pop3-read-timeout))
215                 1000))))
216
217 (defun pop3-movemail (&optional crashbox)
218   "Transfer contents of a maildrop to the specified CRASHBOX."
219   (or crashbox (setq crashbox (expand-file-name "~/.crashbox")))
220   (let* ((process (pop3-open-server pop3-mailhost pop3-port))
221          (crashbuf (get-buffer-create " *pop3-retr*"))
222          (n 1)
223          message-count
224          (pop3-password pop3-password)
225          (pop3-uidl-file-name (convert-standard-filename
226                                (concat pop3-uidl-file-name "-"
227                                        pop3-mailhost)))
228          retrieved-messages messages)
229     ;; for debugging only
230     (if pop3-debug (switch-to-buffer (process-buffer process)))
231     ;; query for password
232     (if (and pop3-password-required (not pop3-password))
233         (setq pop3-password
234               (read-passwd (format "Password for %s: " pop3-maildrop))))
235     (cond ((equal 'apop pop3-authentication-scheme)
236            (pop3-apop process pop3-maildrop))
237           ((equal 'pass pop3-authentication-scheme)
238            (pop3-user process pop3-maildrop)
239            (pop3-pass process))
240           (t (error "Invalid POP3 authentication scheme")))
241     ;; get messages that are suitable for download
242     (message "Retrieving message list...")
243     (setq messages (pop3-get-message-numbers process)
244           message-count (length (cdr messages)))
245     (message "Retrieving message list...%d of %d unread"
246              message-count (pop messages))
247     (unwind-protect
248         (unless (not (stringp crashbox))
249           (while messages
250             (pop3-progress-message
251              "Retrieving message %d of %d (%d octets) from %s..."
252              (floor (* (/ (float n) message-count) 100))
253              n message-count (cdar messages) pop3-mailhost)
254             (pop3-retr process (caar messages) crashbuf)
255             (push (caar messages) retrieved-messages)
256             (setq messages (cdr messages)
257                   n (1+ n)))
258           (with-current-buffer crashbuf
259             (let ((coding-system-for-write 'binary)
260                   jka-compr-compression-info-list jam-zcat-filename-list)
261               (write-region (point-min) (point-max)
262                             crashbox 'append 'nomesg)))
263           ;; mark messages as read
264           (when pop3-leave-mail-on-server
265             (pop3-save-uidls))
266           ;; now delete the messages we have retrieved
267           (unless pop3-leave-mail-on-server
268             (dolist (n retrieved-messages)
269               (message "Deleting message %d of %d from %s..."
270                        n message-count pop3-mailhost)
271               (pop3-dele process n))))
272       (pop3-quit process))
273     (kill-buffer crashbuf)
274     message-count))
275
276 (defun pop3-get-message-count ()
277   "Return the number of messages in the maildrop."
278   (let* ((process (pop3-open-server pop3-mailhost pop3-port))
279          message-count
280          (pop3-password pop3-password))
281     ;; for debugging only
282     (if pop3-debug (switch-to-buffer (process-buffer process)))
283     ;; query for password
284     (if (and pop3-password-required (not pop3-password))
285         (setq pop3-password
286               (read-passwd (format "Password for %s: " pop3-maildrop))))
287     (cond ((equal 'apop pop3-authentication-scheme)
288            (pop3-apop process pop3-maildrop))
289           ((equal 'pass pop3-authentication-scheme)
290            (pop3-user process pop3-maildrop)
291            (pop3-pass process))
292           (t (error "Invalid POP3 authentication scheme")))
293     (setq message-count (car (pop3-stat process)))
294     (pop3-quit process)
295     message-count))
296
297 (defun pop3-open-server (mailhost port)
298   "Open TCP connection to MAILHOST on PORT.
299 Returns the process associated with the connection.
300 Argument PORT specifies connecting port."
301   (let (process)
302     (save-excursion
303       (set-buffer (get-buffer-create (concat " trace of POP session to "
304                                              mailhost)))
305       (erase-buffer)
306       (setq pop3-read-point (point-min))
307       (setq
308        process
309        (cond
310         ((eq pop3-connection-type 'ssl)
311          (pop3-open-ssl-stream "POP" (current-buffer) mailhost port))
312         ((eq pop3-connection-type 'tls)
313          (pop3-open-tls-stream "POP" (current-buffer) mailhost port))
314         (t
315          (let ((coding-system-for-read 'binary)
316                (coding-system-for-write 'binary))
317            (open-network-stream "POP" (current-buffer) mailhost port)))))
318       (let ((response (pop3-read-response process t)))
319         (setq pop3-timestamp
320               (substring response (or (string-match "<" response) 0)
321                          (+ 1 (or (string-match ">" response) -1)))))
322       process)))
323
324 (eval-when-compile
325   (autoload 'open-ssl-stream "ssl"))
326
327 (defun pop3-open-ssl-stream-1 (name buffer host service extra-arg)
328   (require 'ssl)
329   (let* ((ssl-program-name
330           pop3-ssl-program-name)
331          (ssl-program-arguments
332           `(,@pop3-ssl-program-arguments
333             ,extra-arg
334             "-connect" ,(format "%s:%d" host service)))
335          (process (open-ssl-stream name buffer host service)))
336     (when process
337       (with-current-buffer buffer
338         (goto-char (point-min))
339         (while (and (memq (process-status process) '(open run))
340                     (goto-char (point-max))
341                     (forward-line -1)
342                     (not (looking-at "+OK")))
343           (pop3-accept-process-output process)
344           (sit-for 1))
345         (delete-region (point-min) (point)))
346       (and process (memq (process-status process) '(open run))
347            process))))
348
349 (defun pop3-open-ssl-stream (name buffer host service)
350   "Open a SSL connection for a service to a host.
351 Returns a subprocess-object to represent the connection.
352 Args are NAME BUFFER HOST SERVICE."
353   (let (selective-display ;; Disable ^M to nl translation.
354         (coding-system-for-read 'binary)
355         (coding-system-for-write 'binary))
356     (or (pop3-open-ssl-stream-1 name buffer host service "-ssl3")
357         (pop3-open-ssl-stream-1 name buffer host service "-ssl2"))))
358
359 (defun pop3-open-tls-stream (name buffer host service)
360   "Open a TLSv1 connection for a service to a host.
361 Returns a subprocess-object to represent the connection.
362 Args are NAME BUFFER HOST SERVICE."
363   (let* (selective-display ;; Disable ^M to nl translation.
364          (coding-system-for-read 'binary)
365          (coding-system-for-write 'binary)
366          (process (starttls-open-stream name buffer host service)))
367     (pop3-stls process)
368     (starttls-negotiate process)
369     process))
370
371 ;; Support functions
372
373 (defun pop3-process-filter (process output)
374   (save-excursion
375     (set-buffer (process-buffer process))
376     (goto-char (point-max))
377     (insert output)))
378
379 (defun pop3-send-command (process command)
380   (set-buffer (process-buffer process))
381   (goto-char (point-max))
382   ;; (if (= (aref command 0) ?P)
383   ;;     (insert "PASS <omitted>\r\n")
384   ;;   (insert command "\r\n"))
385   (setq pop3-read-point (point))
386   (goto-char (point-max))
387   (process-send-string process (concat command "\r\n")))
388
389 (defun pop3-read-response (process &optional return)
390   "Read the response from the server PROCESS.
391 Return the response string if optional second argument RETURN is non-nil."
392   (let ((case-fold-search nil)
393         match-end)
394     (save-excursion
395       (set-buffer (process-buffer process))
396       (goto-char pop3-read-point)
397       (while (and (memq (process-status process) '(open run))
398                   (not (search-forward "\r\n" nil t)))
399         (pop3-accept-process-output process)
400         (goto-char pop3-read-point))
401       (setq match-end (point))
402       (goto-char pop3-read-point)
403       (if (looking-at "-ERR")
404           (error (buffer-substring (point) (- match-end 2)))
405         (if (not (looking-at "+OK"))
406             (progn (setq pop3-read-point match-end) nil)
407           (setq pop3-read-point match-end)
408           (if return
409               (buffer-substring (point) match-end)
410             t))))))
411
412 (defun pop3-clean-region (start end)
413   (setq end (set-marker (make-marker) end))
414   (save-excursion
415     (goto-char start)
416     (while (and (< (point) end) (search-forward "\r\n" end t))
417       (replace-match "\n" t t))
418     (goto-char start)
419     (while (re-search-forward "\n\n\\(From \\)" end t)
420       (replace-match "\n\n>\\1" t nil))
421     (goto-char start)
422     (while (and (< (point) end) (re-search-forward "^\\." end t))
423       (replace-match "" t t)
424       (forward-char)))
425   (set-marker end nil))
426
427 (eval-when-compile (defvar parse-time-months))
428
429 ;; Copied from message-make-date.
430 (defun pop3-make-date (&optional now)
431   "Make a valid date header.
432 If NOW, use that time instead."
433   (require 'parse-time)
434   (let* ((now (or now (current-time)))
435          (zone (nth 8 (decode-time now)))
436          (sign "+"))
437     (when (< zone 0)
438       (setq sign "-")
439       (setq zone (- zone)))
440     (concat
441      (format-time-string "%d" now)
442      ;; The month name of the %b spec is locale-specific.  Pfff.
443      (format " %s "
444              (capitalize (car (rassoc (nth 4 (decode-time now))
445                                       parse-time-months))))
446      (format-time-string "%Y %H:%M:%S " now)
447      ;; We do all of this because XEmacs doesn't have the %z spec.
448      (format "%s%02d%02d" sign (/ zone 3600) (/ (% zone 3600) 60)))))
449
450 (defun pop3-munge-message-separator (start end)
451   "Check to see if a message separator exists.  If not, generate one."
452   (save-excursion
453     (save-restriction
454       (narrow-to-region start end)
455       (goto-char (point-min))
456       (if (not (or (looking-at "From .?") ; Unix mail
457                    (looking-at "\001\001\001\001\n") ; MMDF
458                    (looking-at "BABYL OPTIONS:"))) ; Babyl
459           (let* ((from (mail-strip-quoted-names (mail-fetch-field "From")))
460                  (tdate (mail-fetch-field "Date"))
461                  (date (split-string (or (and tdate
462                                               (not (string= "" tdate))
463                                               tdate)
464                                          (pop3-make-date))
465                                      " "))
466                  (From_))
467             ;; sample date formats I have seen
468             ;; Date: Tue, 9 Jul 1996 09:04:21 -0400 (EDT)
469             ;; Date: 08 Jul 1996 23:22:24 -0400
470             ;; should be
471             ;; Tue Jul 9 09:04:21 1996
472             (setq date
473                   (cond ((not date)
474                          "Tue Jan 1 00:00:0 1900")
475                         ((string-match "[A-Z]" (nth 0 date))
476                          (format "%s %s %s %s %s"
477                                  (nth 0 date) (nth 2 date) (nth 1 date)
478                                  (nth 4 date) (nth 3 date)))
479                         (t
480                          ;; this really needs to be better but I don't feel
481                          ;; like writing a date to day converter.
482                          (format "Sun %s %s %s %s"
483                                  (nth 1 date) (nth 0 date)
484                                  (nth 3 date) (nth 2 date)))))
485             (setq From_ (format "\nFrom %s  %s\n" from date))
486             (while (string-match "," From_)
487               (setq From_ (concat (substring From_ 0 (match-beginning 0))
488                                   (substring From_ (match-end 0)))))
489             (goto-char (point-min))
490             (insert From_)
491             (if (search-forward "\n\n" nil t)
492                 nil
493               (goto-char (point-max))
494               (insert "\n"))
495             (narrow-to-region (point) (point-max))
496             (let ((size (- (point-max) (point-min))))
497               (goto-char (point-min))
498               (widen)
499               (forward-line -1)
500               (insert (format "Content-Length: %s\n" size))))))))
501
502 ;; UIDL support
503
504 (defun pop3-get-message-numbers (process)
505   "Get the list of message numbers and lengths to retrieve via PROCESS."
506   ;; we use the LIST comand first anyway to get the message lengths.
507   ;; then if we're leaving mail on the server, see if the UIDL command
508   ;; is implemented. if so, we use it to get the message number list.
509   (let* ((messages (pop3-list process))
510          (total (or (pop messages) 0))
511          (uidl (if pop3-leave-mail-on-server
512                    (pop3-get-uidl process)))
513          out)
514     (while messages
515       ;; only retrieve messages matching our regexp or in the uidl list
516       (when (and
517              ;; remove elements not in the uidl, this assumes the uidl is short
518              (or (not (and pop3-leave-mail-on-server
519                            (cdr (assoc pop3-mailhost pop3-uidl-support))))
520                  (memq (caar messages) uidl))
521              (caar messages)
522              ;; don't download messages that are too large
523              (not (and pop3-maximum-message-size
524                        (> (cdar messages) pop3-maximum-message-size)))
525              (not (and pop3-except-header-regexp
526                        (string-match pop3-except-header-regexp
527                                      (pop3-top process (caar messages) 0)))))
528         (push (car messages) out))
529       (setq messages (cdr messages)))
530     (cons total (nreverse out))))
531
532 (defun pop3-get-uidl (process)
533   "Use PROCESS to get a list of unread message numbers."
534   (let ((messages (pop3-uidl process))
535         (support (assoc pop3-mailhost pop3-uidl-support))
536         uidl)
537     (if support
538         (setcdr support (and messages t))
539       (push (cons pop3-mailhost (and messages t))
540             pop3-uidl-support))
541     (when messages
542       (save-excursion
543         (with-temp-buffer
544           (when (file-readable-p pop3-uidl-file-name)
545             (insert-file-contents pop3-uidl-file-name))
546           (goto-char (point-min))
547           (while (looking-at "\\([^ \n\t]+\\)")
548             (set (intern (match-string 1) pop3-uidl-obarray)
549                  (cons nil t))
550             (forward-line 1))))
551       (dolist (message (cdr messages))
552         (if (setq uidl (intern-soft (cdr message) pop3-uidl-obarray))
553             (setcar (symbol-value uidl) (car message))
554           (set (intern (cdr message) pop3-uidl-obarray)
555                (cons (car message) nil))))
556       (pop3-get-unread-message-numbers))))
557
558 (defun pop3-get-unread-message-numbers ()
559   "Return a sorted list of unread msg numbers to retrieve."
560   (let (nums)
561     (mapatoms (lambda (atom)
562                 (if (not (cdr (symbol-value atom)))
563                     (push (car (symbol-value atom)) nums)))
564               pop3-uidl-obarray)
565     (sort nums '<)))
566
567 (defun pop3-save-uidls ()
568   "Save the updated UIDLs to disk for use next time."
569   (when (and pop3-leave-mail-on-server
570              ;; UIDL hash table is non-empty
571              (let ((len (length pop3-uidl-obarray)))
572                (while (< 0 len)
573                  (setq len (if (symbolp (aref pop3-uidl-obarray (1- len)))
574                                -1 (1- len))))
575                (minusp len)))
576     (when (file-readable-p pop3-uidl-file-name)
577       (copy-file pop3-uidl-file-name
578                  (concat pop3-uidl-file-name ".old")
579                  'overwrite 'keeptime))
580     (save-excursion
581       (with-temp-file pop3-uidl-file-name
582         (mapatoms
583          (lambda (atom)
584            (when (car (symbol-value atom))
585              (insert (format "%s\n" atom))))
586          pop3-uidl-obarray)))
587     (fillarray pop3-uidl-obarray 0)))
588
589
590 ;; The Command Set
591
592 ;; AUTHORIZATION STATE
593
594 (defun pop3-user (process user)
595   "Send USER information to POP3 server."
596   (pop3-send-command process (format "USER %s" user))
597   (let ((response (pop3-read-response process t)))
598     (if (not (and response (string-match "+OK" response)))
599         (error (format "USER %s not valid" user)))))
600
601 (defun pop3-pass (process)
602   "Send authentication information to the server."
603   (pop3-send-command process (format "PASS %s" pop3-password))
604   (let ((response (pop3-read-response process t)))
605     (if (not (and response (string-match "+OK" response)))
606         (pop3-quit process))))
607
608 (defun pop3-apop (process user)
609   "Send alternate authentication information to the server."
610   (let ((pass pop3-password))
611     (if (and pop3-password-required (not pass))
612         (setq pass
613               (read-passwd (format "Password for %s: " pop3-maildrop))))
614     (if pass
615         ;; Note that `md5' should never encode a given string to use for
616         ;; the apop authentication, so we should specify `binary'.
617         (let ((hash (md5 (concat pop3-timestamp pass) nil nil 'binary)))
618           (pop3-send-command process (format "APOP %s %s" user hash))
619           (let ((response (pop3-read-response process t)))
620             (if (not (and response (string-match "+OK" response)))
621                 (pop3-quit process)))))))
622
623 (defun pop3-stls (process)
624   "Query whether TLS extension is supported"
625   (pop3-send-command process "STLS")
626   (let ((response (pop3-read-response process t)))
627     (if (not (and response (string-match "+OK" response)))
628         (pop3-quit process))))
629
630 ;; TRANSACTION STATE
631
632 (defun pop3-stat (process)
633   "Return the number of messages in the maildrop and the maildrop's size."
634   (pop3-send-command process "STAT")
635   (let ((response (pop3-read-response process t)))
636     (list (string-to-int (nth 1 (split-string response " ")))
637           (string-to-int (nth 2 (split-string response " "))))))
638
639 (defun pop3-retr (process msg crashbuf)
640   "Retrieve message-id MSG to buffer CRASHBUF."
641   (pop3-send-command process (format "RETR %s" msg))
642   (pop3-read-response process)
643   (save-excursion
644     (let ((region (pop3-get-extended-response process)))
645       (pop3-munge-message-separator (car region) (cadr region))
646       (append-to-buffer crashbuf (car region) (cadr region))
647       (delete-region (car region) (cadr region)))))
648
649 (defun pop3-dele (process msg)
650   "Mark message-id MSG as deleted."
651   (pop3-send-command process (format "DELE %s" msg))
652   (pop3-read-response process))
653
654 (defun pop3-noop (process msg)
655   "No-operation."
656   (pop3-send-command process "NOOP")
657   (pop3-read-response process))
658
659 (defun pop3-last (process)
660   "Return highest accessed message-id number for the session."
661   (pop3-send-command process "LAST")
662   (let ((response (pop3-read-response process t)))
663     (string-to-int (nth 1 (split-string response " ")))))
664
665 (defun pop3-rset (process)
666   "Remove all delete marks from current maildrop."
667   (pop3-send-command process "RSET")
668   (pop3-read-response process))
669
670 ;; UPDATE
671
672 (defun pop3-quit (process)
673   "Close connection to POP3 server.
674 Tell server to remove all messages marked as deleted, unlock the maildrop,
675 and close the connection."
676   (pop3-send-command process "QUIT")
677   (pop3-read-response process t)
678   (when process
679     (save-excursion
680       (set-buffer (process-buffer process))
681       (goto-char (point-max))
682       (delete-process process))))
683
684 (defun pop3-uidl (process &optional msgno)
685   "Return the results of a UIDL command in PROCESS for optional MSGNO.
686 If UIDL is unsupported on this mail server or if msgno is invalid, return nil.
687 Otherwise, return a list in the form
688
689    (N (1 UIDL-1) (2 UIDL-2) ... (N UIDL-N))
690
691 where
692
693    N is an integer for the number of UIDLs returned (could be 0)
694    UIDL-n is a string."
695
696   (if msgno
697       (pop3-send-command process (format "UIDL %d" msgno))
698     (pop3-send-command process "UIDL"))
699
700   (if (null (pop3-read-response process t))
701       nil ;; UIDL is not supported on this server
702     (let (pairs uidl)
703       (save-excursion
704         (save-restriction
705           (apply 'narrow-to-region (pop3-get-extended-response process))
706           (goto-char (point-min))
707           (while (looking-at "\\([^ \n\t]*\\) \\([^ \n\t]*\\)")
708             (setq msgno (string-to-int (match-string 1))
709                   uidl (match-string 2))
710             (push (cons msgno uidl) pairs)
711             (beginning-of-line 2))
712           (cons (length pairs) (nreverse pairs)))))))
713
714 (defun pop3-list (process &optional msgno)
715   "Return the results of a LIST command for PROCESS and optional MSGNO.
716 If (optional) msgno is invalid, return nil.  Otherwise, return a list
717 in the form
718
719    (N (1 LEN-1) (2 LEN-2) ... (N LEN-N))
720
721 where
722
723    N is an integer for the number of msg/len pairs (could be 0)
724    LEN-n is an integer."
725   (if msgno
726       (pop3-send-command process (format "LIST %d" msgno))
727     (pop3-send-command process "LIST"))
728
729   (if (null (pop3-read-response process t))
730       nil ;; MSGNO is not valid number
731     (let (pairs len)
732       (save-excursion
733         (save-restriction
734           (apply 'narrow-to-region (pop3-get-extended-response process))
735           (goto-char (point-min))
736           (while (looking-at "\\([^ \n\t]*\\) \\([^ \n\t]*\\)")
737             (setq msgno (string-to-int (match-string 1))
738                   len (string-to-int (match-string 2)))
739             (push (cons msgno len) pairs)
740             (beginning-of-line 2))
741           (cons (length pairs) (nreverse pairs)))))))
742
743 (defun pop3-top (process msgno &optional lines)
744   "Return the top LINES of messages for PROCESS and MSGNO.
745 If msgno is invalid, return nil.  Otherwise, return a string."
746   (pop3-send-command process (format "TOP %d %d" msgno (or lines 1)))
747   (if (pop3-read-response process t)
748       nil ;; MSGNO is not valid number
749     (save-excursion
750       (apply 'buffer-substring (pop3-get-extended-response process)))))
751
752 ;;; Utility code
753
754 (defun pop3-get-extended-response (process)
755   "Get the extended pop3 response in the PROCESS buffer."
756   (let ((start pop3-read-point) end)
757     (set-buffer (process-buffer process))
758     (goto-char start)
759     (while (not (re-search-forward "^\\.\r\n" nil t))
760       (pop3-accept-process-output process)
761       (goto-char start))
762     (setq pop3-read-point (point-marker))
763     (goto-char (match-beginning 0))
764     (setq end (point-marker))
765     (pop3-clean-region start end)
766     (list start end)))
767
768 ;;; Advise the mail-source function in order to use this module in Gnus.
769
770 (eval-after-load "mail-source"
771   '(if (member '(:connection)
772                (assq 'pop (symbol-value 'mail-source-keyword-map)))
773        nil ;; T-gnus is running.
774      (defadvice mail-source-fetch-pop (around bind-t-gnus-keywords activate)
775        "Bind `pop3-connection-type' and `pop3-leave-mail-on-server' according
776 to `mail-sources' while fetching mails with Gnus."
777        (let ((pop3-connection-type (or (plist-get (cdr (ad-get-arg 0))
778                                                   :connection)
779                                        pop3-connection-type))
780              (pop3-leave-mail-on-server (or (plist-get (cdr (ad-get-arg 0))
781                                                        :leave)
782                                             pop3-leave-mail-on-server)))
783          ad-do-it))))
784
785 \f
786 ;; Summary of POP3 (Post Office Protocol version 3) commands and responses
787
788 ;;; AUTHORIZATION STATE
789
790 ;; Initial TCP connection
791 ;; Arguments: none
792 ;; Restrictions: none
793 ;; Possible responses:
794 ;;  +OK [POP3 server ready]
795
796 ;; USER name
797 ;; Arguments: a server specific user-id (required)
798 ;; Restrictions: authorization state [after unsuccessful USER or PASS
799 ;; Possible responses:
800 ;;  +OK [valid user-id]
801 ;;  -ERR [invalid user-id]
802
803 ;; PASS string
804 ;; Arguments: a server/user-id specific password (required)
805 ;; Restrictions: authorization state, after successful USER
806 ;; Possible responses:
807 ;;  +OK [maildrop locked and ready]
808 ;;  -ERR [invalid password]
809 ;;  -ERR [unable to lock maildrop]
810
811 ;; STLS
812 ;; Arguments: none
813 ;; Restrictions: authorization state
814 ;; Possible responses:
815 ;;  +OK [negotiation is ready]
816 ;;  -ERR [security layer is already active]
817
818 ;;; TRANSACTION STATE
819
820 ;; STAT
821 ;; Arguments: none
822 ;; Restrictions: transaction state
823 ;; Possible responses:
824 ;;  +OK nn mm [# of messages, size of maildrop]
825
826 ;; LIST [msg]
827 ;; Arguments: a message-id (optional)
828 ;; Restrictions: transaction state; msg must not be deleted
829 ;; Possible responses:
830 ;;  +OK [scan listing follows]
831 ;;  -ERR [no such message]
832
833 ;; TOP msg [lines]
834 ;; Arguments: a message-id (required), number of lines (optional)
835 ;; Restrictions: transaction state; msg must not be deleted
836 ;; Possible responses:
837 ;;  +OK [partial message listing follows]
838 ;;  -ERR [no such message]
839
840 ;; UIDL [msg]
841 ;; Arguments: a message-id (optional)
842 ;; Restrictions: transaction state; msg must not be deleted
843 ;; Possible responses:
844 ;;  +OK [uidl listing follows]
845 ;;  -ERR [no such message]
846
847 ;; RETR msg
848 ;; Arguments: a message-id (required)
849 ;; Restrictions: transaction state; msg must not be deleted
850 ;; Possible responses:
851 ;;  +OK [message contents follow]
852 ;;  -ERR [no such message]
853
854 ;; DELE msg
855 ;; Arguments: a message-id (required)
856 ;; Restrictions: transaction state; msg must not be deleted
857 ;; Possible responses:
858 ;;  +OK [message deleted]
859 ;;  -ERR [no such message]
860
861 ;; NOOP
862 ;; Arguments: none
863 ;; Restrictions: transaction state
864 ;; Possible responses:
865 ;;  +OK
866
867 ;; LAST
868 ;; Arguments: none
869 ;; Restrictions: transaction state
870 ;; Possible responses:
871 ;;  +OK nn [highest numbered message accessed]
872
873 ;; RSET
874 ;; Arguments: none
875 ;; Restrictions: transaction state
876 ;; Possible responses:
877 ;;  +OK [all delete marks removed]
878
879 ;;; UPDATE STATE
880
881 ;; QUIT
882 ;; Arguments: none
883 ;; Restrictions: none
884 ;; Possible responses:
885 ;;  +OK [TCP connection closed]
886
887 (provide 'pop3)
888
889 ;;; pop3.el ends here