4dcfc65392baae0dccb3d8b6e97276e8c4330f13
[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 (require 'nnheader)
81
82 (defvar pop3-maildrop (or (user-login-name) (getenv "LOGNAME") (getenv "USER") nil)
83   "*POP3 maildrop.")
84 (defvar pop3-mailhost (or (getenv "MAILHOST") nil)
85   "*POP3 mailhost.")
86 (defvar pop3-port 110
87   "*POP3 port.")
88 (defvar pop3-connection-type nil
89   "*POP3 connection type.")
90
91 (defvar pop3-password-required t
92   "*Non-nil if a password is required when connecting to POP server.")
93 (defvar pop3-password nil
94   "*Password to use when connecting to POP server.")
95
96 (defvar pop3-authentication-scheme 'pass
97   "*POP3 authentication scheme.
98 Defaults to 'pass, for the standard USER/PASS authentication.  Other valid
99 values are 'apop.")
100
101 (defvar pop3-leave-mail-on-server nil
102   "*Non-nil if the mail is to be left on the POP server after fetching.")
103
104 (defvar pop3-timestamp nil
105   "Timestamp returned when initially connected to the POP server.
106 Used for APOP authentication.")
107
108 (defvar pop3-leave-mail-on-server nil
109   "Non-nil if mail is to be left on the server and UIDL used for message retrieval.")
110
111 (defvar pop3-maximum-message-size nil
112   "If non-nil only download messages smaller than this.")
113
114 (defvar pop3-except-header-regexp nil
115   "If non-nil we do not retrieve messages whose headers are matching this regexp.")
116
117 (defvar pop3-uidl-file-name "~/.uidls"
118   "File in which to store the UIDL of processed messages.")
119
120 (defvar pop3-uidl-support nil
121   "Alist of servers and flags of whether they support UIDLs.
122 Users don't have to set this value.")
123
124 (defvar pop3-uidl-obarray (make-vector 31 0)
125   "Uidl hash table.")
126
127 (defvar pop3-read-point nil)
128 (defvar pop3-debug nil)
129
130 (eval-and-compile
131   (autoload 'starttls-open-stream "starttls")
132   (autoload 'starttls-negotiate "starttls"))
133
134 (defvar pop3-ssl-program-name
135   (if (executable-find "openssl")
136       "openssl"
137     "ssleay")
138   "The program to run in a subprocess to open an SSL connection.")
139
140 (defvar pop3-ssl-program-arguments
141   '("s_client" "-quiet")
142   "Arguments to be passed to the program `pop3-ssl-program-name'.")
143
144 (defun pop3-progress-message (format percent &rest args)
145   (apply (function message) format args))
146
147 (defun pop3-movemail (&optional crashbox)
148   "Transfer contents of a maildrop to the specified CRASHBOX."
149   (or crashbox (setq crashbox (expand-file-name "~/.crashbox")))
150   (let* ((process (pop3-open-server pop3-mailhost pop3-port))
151          (crashbuf (get-buffer-create " *pop3-retr*"))
152          (n 1)
153          message-count
154          (pop3-password pop3-password)
155          (pop3-uidl-file-name (convert-standard-filename
156                                (concat pop3-uidl-file-name "-"
157                                        pop3-mailhost)))
158          retrieved-messages messages)
159     ;; for debugging only
160     (if pop3-debug (switch-to-buffer (process-buffer process)))
161     ;; query for password
162     (if (and pop3-password-required (not pop3-password))
163         (setq pop3-password
164               (read-passwd (format "Password for %s: " pop3-maildrop))))
165     (cond ((equal 'apop pop3-authentication-scheme)
166            (pop3-apop process pop3-maildrop))
167           ((equal 'pass pop3-authentication-scheme)
168            (pop3-user process pop3-maildrop)
169            (pop3-pass process))
170           (t (error "Invalid POP3 authentication scheme")))
171     ;; get messages that are suitable for download
172     (message "Retrieving message list...")
173     (setq messages (pop3-get-message-numbers process)
174           message-count (length (cdr messages)))
175     (message "Retrieving message list...%d of %d unread"
176              message-count (pop messages))
177     (unwind-protect
178         (unless (not (stringp crashbox))
179           (while messages
180             (pop3-progress-message
181              "Retrieving message %d of %d (%d octets) from %s..."
182              (floor (* (/ (float n) message-count) 100))
183              n message-count (cdar messages) pop3-mailhost)
184             (pop3-retr process (caar messages) crashbuf)
185             (push (caar messages) retrieved-messages)
186             (setq messages (cdr messages)
187                   n (1+ n)))
188           (with-current-buffer crashbuf
189             (let ((coding-system-for-write 'binary)
190                   jka-compr-compression-info-list jam-zcat-filename-list)
191               (write-region (point-min) (point-max)
192                             crashbox 'append 'nomesg)))
193           ;; mark messages as read
194           (when pop3-leave-mail-on-server
195             (pop3-save-uidls))
196           ;; now delete the messages we have retrieved
197           (unless pop3-leave-mail-on-server
198             (dolist (n retrieved-messages)
199               (message "Deleting message %d of %d from %s..."
200                        n message-count pop3-mailhost)
201               (pop3-dele process n)))
202           )
203       (pop3-quit process))
204     (kill-buffer crashbuf)
205     message-count))
206
207 (defun pop3-get-message-count ()
208   "Return the number of messages in the maildrop."
209   (let* ((process (pop3-open-server pop3-mailhost pop3-port))
210          message-count
211          (pop3-password pop3-password)
212          )
213     ;; for debugging only
214     (if pop3-debug (switch-to-buffer (process-buffer process)))
215     ;; query for password
216     (if (and pop3-password-required (not pop3-password))
217         (setq pop3-password
218               (read-passwd (format "Password for %s: " pop3-maildrop))))
219     (cond ((equal 'apop pop3-authentication-scheme)
220            (pop3-apop process pop3-maildrop))
221           ((equal 'pass pop3-authentication-scheme)
222            (pop3-user process pop3-maildrop)
223            (pop3-pass process))
224           (t (error "Invalid POP3 authentication scheme")))
225     (setq message-count (car (pop3-stat process)))
226     (pop3-quit process)
227     message-count))
228
229 (defun pop3-open-server (mailhost port)
230   "Open TCP connection to MAILHOST on PORT.
231 Returns the process associated with the connection.
232 Argument PORT specifies connecting port."
233   (let (process)
234     (save-excursion
235       (set-buffer (get-buffer-create (concat " trace of POP session to "
236                                              mailhost)))
237       (erase-buffer)
238       (setq pop3-read-point (point-min))
239       (setq
240        process
241        (cond
242         ((eq pop3-connection-type 'ssl)
243          (pop3-open-ssl-stream "POP" (current-buffer) mailhost port))
244         ((eq pop3-connection-type 'tls)
245          (pop3-open-tls-stream "POP" (current-buffer) mailhost port))
246         (t
247          (let ((coding-system-for-read 'binary)
248                (coding-system-for-write 'binary))
249            (open-network-stream "POP" (current-buffer) mailhost port)))))
250       (let ((response (pop3-read-response process t)))
251         (setq pop3-timestamp
252               (substring response (or (string-match "<" response) 0)
253                          (+ 1 (or (string-match ">" response) -1)))))
254       process)))
255
256 (eval-when-compile
257   (autoload 'open-ssl-stream "ssl"))
258
259 (defun pop3-open-ssl-stream-1 (name buffer host service extra-arg)
260   (require 'ssl)
261   (let* ((ssl-program-name
262           pop3-ssl-program-name)
263          (ssl-program-arguments
264           `(,@pop3-ssl-program-arguments
265             ,extra-arg
266             "-connect" ,(format "%s:%d" host service)))
267          (process (open-ssl-stream name buffer host service)))
268     (when process
269       (with-current-buffer buffer
270         (goto-char (point-min))
271         (while (and (memq (process-status process) '(open run))
272                     (goto-char (point-max))
273                     (forward-line -1)
274                     (not (looking-at "+OK")))
275           (nnheader-accept-process-output process)
276           (sit-for 1))
277         (delete-region (point-min) (point)))
278       (and process (memq (process-status process) '(open run))
279            process))))
280
281 (defun pop3-open-ssl-stream (name buffer host service)
282   "Open a SSL connection for a service to a host.
283 Returns a subprocess-object to represent the connection.
284 Args are NAME BUFFER HOST SERVICE."
285   (let (selective-display ;; Disable ^M to nl translation.
286         (coding-system-for-read 'binary)
287         (coding-system-for-write 'binary))
288     (or (pop3-open-ssl-stream-1 name buffer host service "-ssl3")
289         (pop3-open-ssl-stream-1 name buffer host service "-ssl2"))))
290
291 (defun pop3-open-tls-stream (name buffer host service)
292   "Open a TLSv1 connection for a service to a host.
293 Returns a subprocess-object to represent the connection.
294 Args are NAME BUFFER HOST SERVICE."
295   (let* (selective-display ;; Disable ^M to nl translation.
296          (coding-system-for-read 'binary)
297          (coding-system-for-write 'binary)
298          (process (starttls-open-stream name buffer host service)))
299     (pop3-stls process)
300     (starttls-negotiate process)
301     process))
302
303 ;; Support functions
304
305 (defun pop3-process-filter (process output)
306   (save-excursion
307     (set-buffer (process-buffer process))
308     (goto-char (point-max))
309     (insert output)))
310
311 (defun pop3-send-command (process command)
312   (set-buffer (process-buffer process))
313   (goto-char (point-max))
314 ;;  (if (= (aref command 0) ?P)
315 ;;      (insert "PASS <omitted>\r\n")
316 ;;    (insert command "\r\n"))
317   (setq pop3-read-point (point))
318   (goto-char (point-max))
319   (process-send-string process (concat command "\r\n"))
320   )
321
322 (defun pop3-read-response (process &optional return)
323   "Read the response from the server PROCESS.
324 Return the response string if optional second argument RETURN is non-nil."
325   (let ((case-fold-search nil)
326         match-end)
327     (save-excursion
328       (set-buffer (process-buffer process))
329       (goto-char pop3-read-point)
330       (while (and (memq (process-status process) '(open run))
331                   (not (search-forward "\r\n" nil t)))
332         (nnheader-accept-process-output process)
333         (goto-char pop3-read-point))
334       (setq match-end (point))
335       (goto-char pop3-read-point)
336       (if (looking-at "-ERR")
337           (error (buffer-substring (point) (- match-end 2)))
338         (if (not (looking-at "+OK"))
339             (progn (setq pop3-read-point match-end) nil)
340           (setq pop3-read-point match-end)
341           (if return
342               (buffer-substring (point) match-end)
343             t)
344           )))))
345
346 (defun pop3-clean-region (start end)
347   (setq end (set-marker (make-marker) end))
348   (save-excursion
349     (goto-char start)
350     (while (and (< (point) end) (search-forward "\r\n" end t))
351       (replace-match "\n" t t))
352     (goto-char start)
353     (while (re-search-forward "\n\n\\(From \\)" end t)
354       (replace-match "\n\n>\\1" t nil))
355     (goto-char start)
356     (while (and (< (point) end) (re-search-forward "^\\." end t))
357       (replace-match "" t t)
358       (forward-char)))
359   (set-marker end nil))
360
361 (eval-when-compile (defvar parse-time-months))
362
363 ;; Copied from message-make-date.
364 (defun pop3-make-date (&optional now)
365   "Make a valid date header.
366 If NOW, use that time instead."
367   (require 'parse-time)
368   (let* ((now (or now (current-time)))
369          (zone (nth 8 (decode-time now)))
370          (sign "+"))
371     (when (< zone 0)
372       (setq sign "-")
373       (setq zone (- zone)))
374     (concat
375      (format-time-string "%d" now)
376      ;; The month name of the %b spec is locale-specific.  Pfff.
377      (format " %s "
378              (capitalize (car (rassoc (nth 4 (decode-time now))
379                                       parse-time-months))))
380      (format-time-string "%Y %H:%M:%S " now)
381      ;; We do all of this because XEmacs doesn't have the %z spec.
382      (format "%s%02d%02d" sign (/ zone 3600) (/ (% zone 3600) 60)))))
383
384 (defun pop3-munge-message-separator (start end)
385   "Check to see if a message separator exists.  If not, generate one."
386   (save-excursion
387     (save-restriction
388       (narrow-to-region start end)
389       (goto-char (point-min))
390       (if (not (or (looking-at "From .?") ; Unix mail
391                    (looking-at "\001\001\001\001\n") ; MMDF
392                    (looking-at "BABYL OPTIONS:") ; Babyl
393                    ))
394           (let* ((from (mail-strip-quoted-names (mail-fetch-field "From")))
395                  (tdate (mail-fetch-field "Date"))
396                  (date (split-string (or (and tdate
397                                               (not (string= "" tdate))
398                                               tdate)
399                                          (pop3-make-date))
400                                      " "))
401                  (From_))
402             ;; sample date formats I have seen
403             ;; Date: Tue, 9 Jul 1996 09:04:21 -0400 (EDT)
404             ;; Date: 08 Jul 1996 23:22:24 -0400
405             ;; should be
406             ;; Tue Jul 9 09:04:21 1996
407             (setq date
408                   (cond ((not date)
409                          "Tue Jan 1 00:00:0 1900")
410                         ((string-match "[A-Z]" (nth 0 date))
411                          (format "%s %s %s %s %s"
412                                  (nth 0 date) (nth 2 date) (nth 1 date)
413                                  (nth 4 date) (nth 3 date)))
414                         (t
415                          ;; this really needs to be better but I don't feel
416                          ;; like writing a date to day converter.
417                          (format "Sun %s %s %s %s"
418                                  (nth 1 date) (nth 0 date)
419                                  (nth 3 date) (nth 2 date)))
420                         ))
421             (setq From_ (format "\nFrom %s  %s\n" from date))
422             (while (string-match "," From_)
423               (setq From_ (concat (substring From_ 0 (match-beginning 0))
424                                   (substring From_ (match-end 0)))))
425             (goto-char (point-min))
426             (insert From_)
427             (if (search-forward "\n\n" nil t)
428                 nil
429               (goto-char (point-max))
430               (insert "\n"))
431             (narrow-to-region (point) (point-max))
432             (let ((size (- (point-max) (point-min))))
433               (goto-char (point-min))
434               (widen)
435               (forward-line -1)
436               (insert (format "Content-Length: %s\n" size)))
437             )))))
438
439 ;; UIDL support
440
441 (defun pop3-get-message-numbers (process)
442   "Get the list of message numbers and lengths to retrieve via PROCESS."
443   ;; we use the LIST comand first anyway to get the message lengths.
444   ;; then if we're leaving mail on the server, see if the UIDL command
445   ;; is implemented. if so, we use it to get the message number list.
446   (let* ((messages (pop3-list process))
447          (total (or (pop messages) 0))
448          (uidl (if pop3-leave-mail-on-server
449                    (pop3-get-uidl process)))
450          out)
451     (while messages
452       ;; only retrieve messages matching our regexp or in the uidl list
453       (when (and
454              ;; remove elements not in the uidl, this assumes the uidl is short
455              (or (not (and pop3-leave-mail-on-server
456                            (cdr (assoc pop3-mailhost pop3-uidl-support))))
457                  (memq (caar messages) uidl))
458              (caar messages)
459              ;; don't download messages that are too large
460              (not (and pop3-maximum-message-size
461                        (> (cdar messages) pop3-maximum-message-size)))
462              (not (and pop3-except-header-regexp
463                        (string-match pop3-except-header-regexp
464                                      (pop3-top process (caar messages) 0)))))
465         (push (car messages) out))
466       (setq messages (cdr messages)))
467     (cons total (nreverse out))))
468
469 (defun pop3-get-uidl (process)
470   "Use PROCESS to get a list of unread message numbers."
471   (let ((messages (pop3-uidl process))
472         (support (assoc pop3-mailhost pop3-uidl-support))
473         uidl)
474     (if support
475         (setcdr support (and messages t))
476       (push (cons pop3-mailhost (and messages t))
477             pop3-uidl-support))
478     (when messages
479       (save-excursion
480         (with-temp-buffer
481           (when (file-readable-p pop3-uidl-file-name)
482             (insert-file-contents pop3-uidl-file-name))
483           (goto-char (point-min))
484           (while (looking-at "\\([^ \n\t]+\\)")
485             (set (intern (match-string 1) pop3-uidl-obarray)
486                  (cons nil t))
487             (forward-line 1))))
488       (dolist (message (cdr messages))
489         (if (setq uidl (intern-soft (cdr message) pop3-uidl-obarray))
490             (setcar (symbol-value uidl) (car message))
491           (set (intern (cdr message) pop3-uidl-obarray)
492                (cons (car message) nil))))
493       (pop3-get-unread-message-numbers))))
494
495 (defun pop3-get-unread-message-numbers ()
496   "Return a sorted list of unread msg numbers to retrieve."
497   (let (nums)
498     (mapatoms (lambda (atom)
499                 (if (not (cdr (symbol-value atom)))
500                     (push (car (symbol-value atom)) nums)))
501               pop3-uidl-obarray)
502     (sort nums '<)))
503
504 (defun pop3-save-uidls ()
505   "Save the updated UIDLs to disk for use next time."
506   (when (and pop3-leave-mail-on-server
507              ;; UIDL hash table is non-empty
508              (let ((len (length pop3-uidl-obarray)))
509                (while (< 0 len)
510                  (setq len (if (symbolp (aref pop3-uidl-obarray (1- len)))
511                                -1 (1- len))))
512                (minusp len)))
513     (when (file-readable-p pop3-uidl-file-name)
514       (copy-file pop3-uidl-file-name
515                  (concat pop3-uidl-file-name ".old")
516                  'overwrite 'keeptime))
517     (save-excursion
518       (with-temp-file pop3-uidl-file-name
519         (mapatoms
520          (lambda (atom)
521            (when (car (symbol-value atom))
522              (insert (format "%s\n" atom))))
523          pop3-uidl-obarray)))
524     (fillarray pop3-uidl-obarray 0)))
525
526
527 ;; The Command Set
528
529 ;; AUTHORIZATION STATE
530
531 (defun pop3-user (process user)
532   "Send USER information to POP3 server."
533   (pop3-send-command process (format "USER %s" user))
534   (let ((response (pop3-read-response process t)))
535     (if (not (and response (string-match "+OK" response)))
536         (error (format "USER %s not valid" user)))))
537
538 (defun pop3-pass (process)
539   "Send authentication information to the server."
540   (pop3-send-command process (format "PASS %s" pop3-password))
541   (let ((response (pop3-read-response process t)))
542     (if (not (and response (string-match "+OK" response)))
543         (pop3-quit process))))
544
545 (defun pop3-apop (process user)
546   "Send alternate authentication information to the server."
547   (let ((pass pop3-password))
548     (if (and pop3-password-required (not pass))
549         (setq pass
550               (read-passwd (format "Password for %s: " pop3-maildrop))))
551     (if pass
552         ;; Note that `md5' should never encode a given string to use for
553         ;; the apop authentication, so we should specify `binary'.
554         (let ((hash (md5 (concat pop3-timestamp pass) nil nil 'binary)))
555           (pop3-send-command process (format "APOP %s %s" user hash))
556           (let ((response (pop3-read-response process t)))
557             (if (not (and response (string-match "+OK" response)))
558                 (pop3-quit process)))))
559     ))
560
561 (defun pop3-stls (process)
562   "Query whether TLS extension is supported"
563   (pop3-send-command process "STLS")
564   (let ((response (pop3-read-response process t)))
565     (if (not (and response (string-match "+OK" response)))
566         (pop3-quit process))))
567
568 ;; TRANSACTION STATE
569
570 (defun pop3-stat (process)
571   "Return the number of messages in the maildrop and the maildrop's size."
572   (pop3-send-command process "STAT")
573   (let ((response (pop3-read-response process t)))
574     (list (string-to-int (nth 1 (split-string response " ")))
575           (string-to-int (nth 2 (split-string response " "))))
576     ))
577
578 (defun pop3-retr (process msg crashbuf)
579   "Retrieve message-id MSG to buffer CRASHBUF."
580   (pop3-send-command process (format "RETR %s" msg))
581   (pop3-read-response process)
582   (save-excursion
583     (let ((region (pop3-get-extended-response process)))
584       (pop3-munge-message-separator (car region) (cadr region))
585       (append-to-buffer crashbuf (car region) (cadr region))
586       (delete-region (car region) (cadr region))
587       )))
588
589 (defun pop3-dele (process msg)
590   "Mark message-id MSG as deleted."
591   (pop3-send-command process (format "DELE %s" msg))
592   (pop3-read-response process))
593
594 (defun pop3-noop (process msg)
595   "No-operation."
596   (pop3-send-command process "NOOP")
597   (pop3-read-response process))
598
599 (defun pop3-last (process)
600   "Return highest accessed message-id number for the session."
601   (pop3-send-command process "LAST")
602   (let ((response (pop3-read-response process t)))
603     (string-to-int (nth 1 (split-string response " ")))
604     ))
605
606 (defun pop3-rset (process)
607   "Remove all delete marks from current maildrop."
608   (pop3-send-command process "RSET")
609   (pop3-read-response process))
610
611 ;; UPDATE
612
613 (defun pop3-quit (process)
614   "Close connection to POP3 server.
615 Tell server to remove all messages marked as deleted, unlock the maildrop,
616 and close the connection."
617   (pop3-send-command process "QUIT")
618   (pop3-read-response process t)
619   (when process
620     (save-excursion
621       (set-buffer (process-buffer process))
622       (goto-char (point-max))
623       (delete-process process))))
624
625 (defun pop3-uidl (process &optional msgno)
626   "Return the results of a UIDL command in PROCESS for optional MSGNO.
627 If UIDL is unsupported on this mail server or if msgno is invalid, return nil.
628 Otherwise, return a list in the form
629
630    (N (1 UIDL-1) (2 UIDL-2) ... (N UIDL-N))
631
632 where
633
634    N is an integer for the number of UIDLs returned (could be 0)
635    UIDL-n is a string."
636
637   (if msgno
638       (pop3-send-command process (format "UIDL %d" msgno))
639     (pop3-send-command process "UIDL"))
640
641   (if (null (pop3-read-response process t))
642       nil ;; UIDL is not supported on this server
643     (let (pairs uidl)
644       (save-excursion
645         (save-restriction
646           (apply 'narrow-to-region (pop3-get-extended-response process))
647           (goto-char (point-min))
648           (while (looking-at "\\([^ \n\t]*\\) \\([^ \n\t]*\\)")
649             (setq msgno (string-to-int (match-string 1))
650                   uidl (match-string 2))
651             (push (cons msgno uidl) pairs)
652             (beginning-of-line 2))
653           (cons (length pairs) (nreverse pairs))
654           )))))
655
656 (defun pop3-list (process &optional msgno)
657   "Return the results of a LIST command for PROCESS and optional MSGNO.
658 If (optional) msgno is invalid, return nil.  Otherwise, return a list
659 in the form
660
661    (N (1 LEN-1) (2 LEN-2) ... (N LEN-N))
662
663 where
664
665    N is an integer for the number of msg/len pairs (could be 0)
666    LEN-n is an integer."
667   (if msgno
668       (pop3-send-command process (format "LIST %d" msgno))
669     (pop3-send-command process "LIST"))
670
671   (if (null (pop3-read-response process t))
672       nil ;; MSGNO is not valid number
673     (let (pairs len)
674       (save-excursion
675         (save-restriction
676           (apply 'narrow-to-region (pop3-get-extended-response process))
677           (goto-char (point-min))
678           (while (looking-at "\\([^ \n\t]*\\) \\([^ \n\t]*\\)")
679             (setq msgno (string-to-int (match-string 1))
680                   len (string-to-int (match-string 2)))
681             (push (cons msgno len) pairs)
682             (beginning-of-line 2))
683           (cons (length pairs) (nreverse pairs))
684           )))))
685
686 (defun pop3-top (process msgno &optional lines)
687   "Return the top LINES of messages for PROCESS and MSGNO.
688 If msgno is invalid, return nil.  Otherwise, return a string."
689   (pop3-send-command process (format "TOP %d %d" msgno (or lines 1)))
690   (if (pop3-read-response process t)
691       nil ;; MSGNO is not valid number
692     (save-excursion
693       (apply 'buffer-substring (pop3-get-extended-response process)))
694     ))
695
696 ;;; Utility code
697
698 (defun pop3-get-extended-response (process)
699   "Get the extended pop3 response in the PROCESS buffer."
700   (let ((start pop3-read-point) end)
701     (set-buffer (process-buffer process))
702     (goto-char start)
703     (while (not (re-search-forward "^\\.\r\n" nil t))
704       ;; Fixme: Shouldn't depend on nnheader.
705       (nnheader-accept-process-output process)
706       (goto-char start))
707     (setq pop3-read-point (point-marker))
708     (goto-char (match-beginning 0))
709     (setq end (point-marker))
710     (pop3-clean-region start end)
711     (list start end)))
712
713 ;;; Advise the mail-source function in order to use this module in Gnus.
714
715 (eval-after-load "mail-source"
716   '(if (member '(:connection)
717                (assq 'pop (symbol-value 'mail-source-keyword-map)))
718        nil ;; T-gnus is running.
719      (defadvice mail-source-fetch-pop (around bind-t-gnus-keywords activate)
720        "Bind `pop3-connection-type' and `pop3-leave-mail-on-server' according
721 to `mail-sources' while fetching mails with Gnus."
722        (let ((pop3-connection-type (or (plist-get (cdr (ad-get-arg 0))
723                                                   :connection)
724                                        pop3-connection-type))
725              (pop3-leave-mail-on-server (or (plist-get (cdr (ad-get-arg 0))
726                                                        :leave)
727                                             pop3-leave-mail-on-server)))
728          ad-do-it))))
729
730 \f
731 ;; Summary of POP3 (Post Office Protocol version 3) commands and responses
732
733 ;;; AUTHORIZATION STATE
734
735 ;; Initial TCP connection
736 ;; Arguments: none
737 ;; Restrictions: none
738 ;; Possible responses:
739 ;;  +OK [POP3 server ready]
740
741 ;; USER name
742 ;; Arguments: a server specific user-id (required)
743 ;; Restrictions: authorization state [after unsuccessful USER or PASS
744 ;; Possible responses:
745 ;;  +OK [valid user-id]
746 ;;  -ERR [invalid user-id]
747
748 ;; PASS string
749 ;; Arguments: a server/user-id specific password (required)
750 ;; Restrictions: authorization state, after successful USER
751 ;; Possible responses:
752 ;;  +OK [maildrop locked and ready]
753 ;;  -ERR [invalid password]
754 ;;  -ERR [unable to lock maildrop]
755
756 ;; STLS
757 ;; Arguments: none
758 ;; Restrictions: authorization state
759 ;; Possible responses:
760 ;;  +OK [negotiation is ready]
761 ;;  -ERR [security layer is already active]
762
763 ;;; TRANSACTION STATE
764
765 ;; STAT
766 ;; Arguments: none
767 ;; Restrictions: transaction state
768 ;; Possible responses:
769 ;;  +OK nn mm [# of messages, size of maildrop]
770
771 ;; LIST [msg]
772 ;; Arguments: a message-id (optional)
773 ;; Restrictions: transaction state; msg must not be deleted
774 ;; Possible responses:
775 ;;  +OK [scan listing follows]
776 ;;  -ERR [no such message]
777
778 ;; TOP msg [lines]
779 ;; Arguments: a message-id (required), number of lines (optional)
780 ;; Restrictions: transaction state; msg must not be deleted
781 ;; Possible responses:
782 ;;  +OK [partial message listing follows]
783 ;;  -ERR [no such message]
784
785 ;; UIDL [msg]
786 ;; Arguments: a message-id (optional)
787 ;; Restrictions: transaction state; msg must not be deleted
788 ;; Possible responses:
789 ;;  +OK [uidl listing follows]
790 ;;  -ERR [no such message]
791
792 ;; RETR msg
793 ;; Arguments: a message-id (required)
794 ;; Restrictions: transaction state; msg must not be deleted
795 ;; Possible responses:
796 ;;  +OK [message contents follow]
797 ;;  -ERR [no such message]
798
799 ;; DELE msg
800 ;; Arguments: a message-id (required)
801 ;; Restrictions: transaction state; msg must not be deleted
802 ;; Possible responses:
803 ;;  +OK [message deleted]
804 ;;  -ERR [no such message]
805
806 ;; NOOP
807 ;; Arguments: none
808 ;; Restrictions: transaction state
809 ;; Possible responses:
810 ;;  +OK
811
812 ;; LAST
813 ;; Arguments: none
814 ;; Restrictions: transaction state
815 ;; Possible responses:
816 ;;  +OK nn [highest numbered message accessed]
817
818 ;; RSET
819 ;; Arguments: none
820 ;; Restrictions: transaction state
821 ;; Possible responses:
822 ;;  +OK [all delete marks removed]
823
824 ;;; UPDATE STATE
825
826 ;; QUIT
827 ;; Arguments: none
828 ;; Restrictions: none
829 ;; Possible responses:
830 ;;  +OK [TCP connection closed]
831
832 (provide 'pop3)
833
834 ;;; pop3.el ends here