1 ;;; elmo-pop3.el -- POP3 Interface for ELMO.
3 ;; Copyright 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
5 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Keywords: mail, net news
7 ;; Time-stamp: <00/04/28 10:28:08 teranisi>
9 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
42 (defun-maybe sasl-digest-md5-digest-response
43 (digest-challenge username passwd serv-type host &optional realm))
44 (defun-maybe sasl-scram-md5-client-msg-1
45 (authenticate-id &optional authorize-id))
46 (defun-maybe sasl-scram-md5-client-msg-2
47 (server-msg-1 client-msg-1 salted-pass))
48 (defun-maybe sasl-scram-md5-make-salted-pass
49 (server-msg-1 passphrase))
50 (defun-maybe sasl-scram-md5-authenticate-server
51 (server-msg-1 server-msg-2 client-msg-1 salted-pass))
52 (defun-maybe starttls-negotiate (a)))
58 (defvar elmo-pop3-exists-exactly t)
59 (defvar elmo-pop3-read-point nil)
60 (defvar elmo-pop3-connection-cache nil
61 "Cache of pop3 connection.")
63 (defun elmo-pop3-close-connection (connection &optional process buffer)
65 (let* ((buffer (or buffer (nth 0 connection)))
66 (process (or process (nth 1 connection))))
67 (elmo-pop3-send-command buffer process "quit")
68 (when (null (elmo-pop3-read-response buffer process t))
69 (error "POP error: QUIT failed")))))
71 (defun elmo-pop3-flush-connection ()
73 (let ((cache elmo-pop3-connection-cache)
74 buffer process proc-stat)
76 (setq buffer (car (cdr (car cache))))
77 (setq process (car (cdr (cdr (car cache)))))
79 (not (or (eq (setq proc-stat
80 (process-status process))
82 (eq proc-stat 'exit))))
84 (elmo-pop3-close-connection nil process buffer)
86 (if buffer (kill-buffer buffer))
87 ;;(setq process (car (cdr (cdr (car cache)))))
88 (if process (delete-process process))
89 (setq cache (cdr cache)))
90 (setq elmo-pop3-connection-cache nil)))
92 (defun elmo-pop3-get-connection (spec)
93 (let* ((user (elmo-pop3-spec-username spec))
94 (server (elmo-pop3-spec-hostname spec))
95 (port (elmo-pop3-spec-port spec))
96 (auth (elmo-pop3-spec-auth spec))
97 (ssl (elmo-pop3-spec-ssl spec))
98 (user-at-host (format "%s@%s" user server))
99 ret-val result buffer process errmsg proc-stat
100 user-at-host-on-port)
101 (if (not (elmo-plugged-p server port))
103 (setq user-at-host-on-port
104 (concat user-at-host ":" (int-to-string port)
105 (if (eq ssl 'starttls) "!!" (if ssl "!"))))
106 (setq ret-val (assoc user-at-host-on-port elmo-pop3-connection-cache))
108 (or (eq (setq proc-stat
109 (process-status (cadr (cdr ret-val))))
111 (eq proc-stat 'exit)))
112 ;; connection is closed...
114 (kill-buffer (car (cdr ret-val)))
115 (setq elmo-pop3-connection-cache
116 (delete ret-val elmo-pop3-connection-cache))
122 (elmo-pop3-open-connection
123 server user port auth
124 (elmo-get-passwd user-at-host) ssl))
126 (error "Connection failed"))
127 (setq buffer (car result))
128 (setq process (cdr result))
129 (when (and process (null buffer))
130 (elmo-remove-passwd user-at-host)
131 (delete-process process)
132 (error "Login failed")
134 (setq elmo-pop3-connection-cache
135 (append elmo-pop3-connection-cache
137 (cons user-at-host-on-port
138 (setq ret-val (list buffer process))))))
141 (defun elmo-pop3-send-command (buffer process command)
145 (goto-char (point-min))
146 (setq elmo-pop3-read-point (point))
147 (process-send-string process command)
148 (process-send-string process "\r\n")))
150 (defun elmo-pop3-send-command-no-erase (buffer process command)
154 (goto-char (point-min))
155 (setq elmo-pop3-read-point (point))
156 (process-send-string process command)
157 (process-send-string process "\r\n")))
159 (defun elmo-pop3-read-response (buffer process &optional not-command)
162 (let ((case-fold-search nil)
163 (response-string nil)
164 (response-continue t)
167 (while response-continue
168 (goto-char elmo-pop3-read-point)
169 (while (not (re-search-forward "\r?\n" nil t))
170 (accept-process-output process)
171 (goto-char elmo-pop3-read-point))
172 (setq match-end (point))
173 (setq response-string
174 (buffer-substring elmo-pop3-read-point (- match-end 2)))
175 (goto-char elmo-pop3-read-point)
176 (if (looking-at "\\+.*$")
178 (setq response-continue nil)
179 (setq elmo-pop3-read-point match-end)
182 (concat return-value "\n" response-string)
185 (if (looking-at "\\-.*$")
187 (setq response-continue nil)
188 (setq elmo-pop3-read-point match-end)
189 (setq return-value nil))
190 (setq elmo-pop3-read-point match-end)
192 (setq response-continue nil))
195 (concat return-value "\n" response-string)
197 (setq elmo-pop3-read-point match-end)))
200 (defun elmo-pop3-process-filter (process output)
202 (set-buffer (process-buffer process))
203 (goto-char (point-max))
206 (defun elmo-pop3-open-connection (server user port auth passphrase ssl)
209 process-buffer ret-val response capability)
213 (get-buffer-create (format " *POP session to %s:%d" host port)))
215 (set-buffer process-buffer)
216 (elmo-set-buffer-multibyte nil)
219 (elmo-open-network-stream "POP" process-buffer host port ssl))
220 (and (null process) (throw 'done nil))
221 (set-process-filter process 'elmo-pop3-process-filter)
222 ;; flush connections when exiting...
224 (set-buffer process-buffer)
225 (make-local-variable 'elmo-pop3-read-point)
226 (setq elmo-pop3-read-point (point-min))
227 (when (null (setq response
228 (elmo-pop3-read-response process-buffer process t)))
229 (setq ret-val (cons nil process))
231 (when (eq ssl 'starttls)
232 (elmo-pop3-send-command process-buffer process "stls")
233 (string-match "^\+OK"
234 (elmo-pop3-read-response
235 process-buffer process))
236 (starttls-negotiate process))
237 (cond ((string= auth "apop")
239 (if (string-match "^\+OK .*\\(<[^\>]+>\\)" response)
240 ;; good, APOP ready server
243 (elmo-pop3-send-command
244 process-buffer process
248 (concat (match-string 1 response)
250 ;; otherwise, fail (only APOP authentication)
251 (setq ret-val (cons nil process))
253 ((string= auth "cram-md5")
254 (elmo-pop3-send-command
255 process-buffer process "auth cram-md5")
256 (when (null (setq response
257 (elmo-pop3-read-response
258 process-buffer process t)))
259 (setq ret-val (cons nil process))
261 (elmo-pop3-send-command
262 process-buffer process
263 (elmo-base64-encode-string
264 (sasl-cram-md5 user passphrase
265 (elmo-base64-decode-string
266 (cadr (split-string response " ")))))))
267 ((string= auth "digest-md5")
268 (elmo-pop3-send-command
269 process-buffer process "auth digest-md5")
270 (when (null (setq response
271 (elmo-pop3-read-response
272 process-buffer process t)))
273 (setq ret-val (cons nil process))
275 (elmo-pop3-send-command
276 process-buffer process
277 (elmo-base64-encode-string
278 (sasl-digest-md5-digest-response
279 (elmo-base64-decode-string
280 (cadr (split-string response " ")))
281 user passphrase "pop" host)
283 (when (null (setq response
284 (elmo-pop3-read-response
285 process-buffer process t)))
286 (setq ret-val (cons nil process))
288 (elmo-pop3-send-command process-buffer process ""))
289 ((string= auth "scram-md5")
290 (let (server-msg-1 server-msg-2 client-msg-1 client-msg-2
292 (elmo-pop3-send-command
293 process-buffer process
294 (format "auth scram-md5 %s"
295 (elmo-base64-encode-string
297 (sasl-scram-md5-client-msg-1 user)))))
298 (when (null (setq response
299 (elmo-pop3-read-response
300 process-buffer process t)))
301 (setq ret-val (cons nil process))
304 (elmo-base64-decode-string
305 (cadr (split-string response " "))))
306 (elmo-pop3-send-command
307 process-buffer process
308 (elmo-base64-encode-string
309 (sasl-scram-md5-client-msg-2
313 (sasl-scram-md5-make-salted-pass
314 server-msg-1 passphrase)))))
315 (when (null (setq response
316 (elmo-pop3-read-response
317 process-buffer process t)))
318 (setq ret-val (cons nil process))
321 (elmo-base64-decode-string
322 (cadr (split-string response " "))))
323 (if (null (sasl-scram-md5-authenticate-server
329 (elmo-pop3-send-command
330 process-buffer process "") ))
333 (elmo-pop3-send-command process-buffer process
334 (format "user %s" user))
335 (when (null (elmo-pop3-read-response process-buffer process t))
336 (setq ret-val (cons nil process))
338 (elmo-pop3-send-command process-buffer process
339 (format "pass %s" passphrase))))
340 ;; read PASS or APOP response
341 (when (null (elmo-pop3-read-response process-buffer process t))
342 (setq ret-val (cons nil process))
344 (setq ret-val (cons process-buffer process)))))
347 (defun elmo-pop3-read-contents (buffer process)
350 (let ((case-fold-search nil)
352 (goto-char elmo-pop3-read-point)
353 (while (not (re-search-forward "^\\.\r\n" nil t))
354 (accept-process-output process)
355 (goto-char elmo-pop3-read-point))
356 (setq match-end (point))
358 (buffer-substring elmo-pop3-read-point
362 (defun elmo-pop3-list-folders (spec &optional hierarchy) nil)
363 (defun elmo-pop3-append-msg (spec string) nil nil)
364 (defun elmo-pop3-folder-creatable-p (spec) nil)
365 (defun elmo-pop3-create-folder (spec) nil)
367 (defun elmo-pop3-folder-exists-p (spec)
368 (if (and elmo-pop3-exists-exactly
369 (elmo-pop3-plugged-p spec))
371 (let (elmo-auto-change-plugged) ;;don't change plug status.
374 (elmo-pop3-get-connection spec)
375 (elmo-pop3-flush-connection))
379 (defun elmo-pop3-parse-list-response (string)
381 (let ((tmp-buffer (get-buffer-create " *ELMO PARSE TMP*"))
383 (set-buffer tmp-buffer)
384 (let ((case-fold-search t))
387 (goto-char (point-min))
388 (while (re-search-forward "^\\([0-9]*\\)[\t ].*$" nil t)
392 (elmo-match-buffer 1))
394 (kill-buffer tmp-buffer)
395 (nreverse ret-val)))))
397 (defun elmo-pop3-list-folder (spec)
399 (elmo-pop3-flush-connection)
400 (let* ((connection (elmo-pop3-get-connection spec))
401 (buffer (nth 0 connection))
402 (process (nth 1 connection))
403 response errmsg ret-val)
404 (elmo-pop3-send-command buffer process "list")
405 (if (null (elmo-pop3-read-response buffer process))
406 (error "POP List folder failed"))
407 (if (null (setq response (elmo-pop3-read-contents buffer process)))
408 (error "POP List folder failed"))
409 ;; POP server always returns a sequence of serial numbers.
410 (elmo-pop3-parse-list-response response))))
412 (defun elmo-pop3-max-of-folder (spec)
414 (elmo-pop3-flush-connection)
415 (let* ((connection (elmo-pop3-get-connection spec))
416 (buffer (nth 0 connection))
417 (process (nth 1 connection))
420 (elmo-pop3-send-command buffer process "STAT")
421 (setq response (elmo-pop3-read-response buffer process))
422 ;; response: "^\+OK 2 7570$"
423 (if (not (string-match "^\+OK[ \t]*\\([0-9]*\\)" response))
424 (error "POP STAT command failed")
427 (substring response (match-beginning 1)(match-end 1 ))))
428 (cons total total)))))
430 (defvar elmo-pop3-header-fetch-chop-length 200)
432 (defsubst elmo-pop3-next-result-arrived-p ()
434 ((eq (following-char) ?+)
435 (if (re-search-forward "\n\\.\r?\n" nil t)
439 (if (search-forward "\n" nil t)
445 (defun elmo-pop3-retrieve-headers (buffer tobuffer process articles)
449 (let ((number (length articles))
452 (last-point (point-min)))
453 ;; Send HEAD commands.
455 (elmo-pop3-send-command-no-erase
458 (format "top %s 0" (car articles))
460 ; (accept-process-output process 1)
461 (setq articles (cdr articles))
462 (setq count (1+ count))
463 ;; Every 200 requests we have to read the stream in
464 ;; order to avoid deadlocks.
465 (when (or elmo-pop3-send-command-synchronously
466 (null articles) ;All requests have been sent.
467 (zerop (% count elmo-pop3-header-fetch-chop-length)))
468 (unless elmo-pop3-send-command-synchronously
469 (accept-process-output process 1))
473 (goto-char last-point)
475 (while (elmo-pop3-next-result-arrived-p)
476 (setq last-point (point))
477 (setq received (1+ received)))
479 (and (zerop (% received 20))
480 (elmo-display-progress
481 'elmo-pop3-retrieve-headers "Getting headers..."
482 (/ (* received 100) number)))
483 (accept-process-output process 1)
484 ;(accept-process-output process)
487 (elmo-display-progress
488 'elmo-pop3-retrieve-headers "Getting headers..." 100)
489 ;; Remove all "\r"'s.
490 (goto-char (point-min))
491 (while (search-forward "\r\n" nil t)
492 (replace-match "\n"))
493 (copy-to-buffer tobuffer (point-min) (point-max))
494 ;(elmo-pop3-close-connection nil process buffer) ; close connection
497 (defalias 'elmo-pop3-msgdb-create 'elmo-pop3-msgdb-create-as-numlist)
498 (defun elmo-pop3-msgdb-create-as-numlist (spec numlist new-mark
499 already-mark seen-mark
500 important-mark seen-list)
502 (let* ((connection (elmo-pop3-get-connection spec))
503 (buffer (nth 0 connection))
504 (process (nth 1 connection))
505 response errmsg ret-val)
506 (elmo-pop3-msgdb-create-by-header buffer process numlist
507 new-mark already-mark
508 seen-mark seen-list))))
510 (defun elmo-pop3-msgdb-create-by-header (buffer process numlist
511 new-mark already-mark
514 (let ((tmp-buffer (get-buffer-create " *ELMO Overview TMP*"))
516 (elmo-pop3-retrieve-headers
517 buffer tmp-buffer process numlist)
519 (elmo-pop3-msgdb-create-message
523 new-mark already-mark seen-mark seen-list))
524 (kill-buffer tmp-buffer)
527 (defun elmo-pop3-msgdb-create-message (buffer
528 num numlist new-mark already-mark
533 overview number-alist mark-alist
534 entity i number message-id gmark seen)
536 (elmo-set-buffer-multibyte default-enable-multibyte-characters)
537 (goto-char (point-min))
539 (message "Creating msgdb...")
541 (setq beg (save-excursion (forward-line 1) (point)))
542 (elmo-pop3-next-result-arrived-p)
546 (narrow-to-region beg (point))
548 (elmo-msgdb-create-overview-from-buffer
550 (setq numlist (cdr numlist))
553 (elmo-msgdb-append-element
556 (elmo-msgdb-number-add number-alist
557 (elmo-msgdb-overview-entity-get-number entity)
559 (setq message-id (car entity))
560 (setq seen (member message-id seen-list))
561 (if (setq gmark (or (elmo-msgdb-global-mark-get message-id)
562 (if (elmo-cache-exists-p
568 (if elmo-pop3-use-cache
572 (elmo-msgdb-mark-append
574 (elmo-msgdb-overview-entity-get-number entity)
578 (and (zerop (% i 20))
579 (elmo-display-progress
580 'elmo-pop3-msgdb-create-message "Creating msgdb..."
583 (elmo-display-progress
584 'elmo-pop3-msgdb-create-message "Creating msgdb..." 100)
585 (list overview number-alist mark-alist))))
587 (defun elmo-pop3-read-body (buffer process outbuf)
588 (with-current-buffer buffer
589 (let ((start elmo-pop3-read-point)
592 (while (not (re-search-forward "^\\.\r?\n" nil t))
593 (accept-process-output process)
596 (with-current-buffer outbuf
598 (insert-buffer-substring buffer start (- end 3))
599 (elmo-delete-cr-get-content-type)))))
601 (defun elmo-pop3-read-msg (spec number outbuf)
603 (let* ((connection (elmo-pop3-get-connection spec))
604 (buffer (car connection))
605 (process (cadr connection))
606 (cwf (caddr connection))
608 (elmo-pop3-send-command buffer process
609 (format "retr %s" number))
610 (when (null (setq response (elmo-pop3-read-response
612 (error "Fetching message failed"))
613 (setq response (elmo-pop3-read-body buffer process outbuf))
615 (goto-char (point-min))
616 (while (re-search-forward "^\\." nil t)
621 (defun elmo-pop3-delete-msg (buffer process number)
622 (let (response errmsg msg)
623 (elmo-pop3-send-command buffer process
624 (format "dele %s" number))
625 (when (null (setq response (elmo-pop3-read-response
627 (error "Deleting message failed"))))
629 (defun elmo-pop3-delete-msgs (spec msgs)
631 (let* ((connection (elmo-pop3-get-connection spec))
632 (buffer (car connection))
633 (process (cadr connection)))
634 (mapcar '(lambda (msg) (elmo-pop3-delete-msg
638 (defun elmo-pop3-search (spec condition &optional numlist)
639 (error "Searching in pop3 folder is not implemented yet"))
641 (defun elmo-pop3-use-cache-p (spec number)
644 (defun elmo-pop3-local-file-p (spec number)
647 (defun elmo-pop3-port-label (spec)
649 (if (elmo-pop3-spec-ssl spec) "!ssl" "")))
651 (defsubst elmo-pop3-portinfo (spec)
652 (list (elmo-pop3-spec-hostname spec)
653 (elmo-pop3-spec-port spec)))
655 (defun elmo-pop3-plugged-p (spec)
656 (apply 'elmo-plugged-p
657 (append (elmo-pop3-portinfo spec)
658 (list nil (quote (elmo-pop3-port-label spec))))))
660 (defun elmo-pop3-set-plugged (spec plugged add)
661 (apply 'elmo-set-plugged plugged
662 (append (elmo-pop3-portinfo spec)
663 (list nil nil (quote (elmo-pop3-port-label spec)) add))))
665 (defalias 'elmo-pop3-sync-number-alist
666 'elmo-generic-sync-number-alist)
667 (defalias 'elmo-pop3-list-folder-unread
668 'elmo-generic-list-folder-unread)
669 (defalias 'elmo-pop3-list-folder-important
670 'elmo-generic-list-folder-important)
671 (defalias 'elmo-pop3-commit 'elmo-generic-commit)
675 ;;; elmo-pop3.el ends here