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
8 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; This program 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.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
41 (defun-maybe sasl-digest-md5-digest-response
42 (digest-challenge username passwd serv-type host &optional realm))
43 (defun-maybe sasl-scram-md5-client-msg-1
44 (authenticate-id &optional authorize-id))
45 (defun-maybe sasl-scram-md5-client-msg-2
46 (server-msg-1 client-msg-1 salted-pass))
47 (defun-maybe sasl-scram-md5-make-salted-pass
48 (server-msg-1 passphrase))
49 (defun-maybe sasl-scram-md5-authenticate-server
50 (server-msg-1 server-msg-2 client-msg-1 salted-pass))
51 (defun-maybe starttls-negotiate (a)))
57 (defvar elmo-pop3-exists-exactly t)
58 (defvar elmo-pop3-read-point nil)
59 (defvar elmo-pop3-connection-cache nil
60 "Cache of pop3 connection.")
62 (defun elmo-pop3-close-connection (connection &optional process buffer)
64 (let* ((buffer (or buffer (nth 0 connection)))
65 (process (or process (nth 1 connection))))
66 (elmo-pop3-send-command buffer process "quit")
67 (when (null (elmo-pop3-read-response buffer process t))
68 (error "POP error: QUIT failed")))))
70 (defun elmo-pop3-flush-connection ()
72 (let ((cache elmo-pop3-connection-cache)
73 buffer process proc-stat)
75 (setq buffer (car (cdr (car cache))))
76 (setq process (car (cdr (cdr (car cache)))))
78 (not (or (eq (setq proc-stat
79 (process-status process))
81 (eq proc-stat 'exit))))
83 (elmo-pop3-close-connection nil process buffer)
85 (if buffer (kill-buffer buffer))
86 ;;(setq process (car (cdr (cdr (car cache)))))
87 (if process (delete-process process))
88 (setq cache (cdr cache)))
89 (setq elmo-pop3-connection-cache nil)))
91 (defun elmo-pop3-get-connection (spec)
92 (let* ((user (elmo-pop3-spec-username spec))
93 (server (elmo-pop3-spec-hostname spec))
94 (port (elmo-pop3-spec-port spec))
95 (auth (elmo-pop3-spec-auth spec))
96 (ssl (elmo-pop3-spec-ssl spec))
97 (user-at-host (format "%s@%s" user server))
98 ret-val result buffer process errmsg proc-stat
100 (if (not (elmo-plugged-p server port))
102 (setq user-at-host-on-port
103 (concat user-at-host ":" (int-to-string port)
104 (if (eq ssl 'starttls) "!!" (if ssl "!"))))
105 (setq ret-val (assoc user-at-host-on-port elmo-pop3-connection-cache))
107 (or (eq (setq proc-stat
108 (process-status (cadr (cdr ret-val))))
110 (eq proc-stat 'exit)))
111 ;; connection is closed...
113 (kill-buffer (car (cdr ret-val)))
114 (setq elmo-pop3-connection-cache
115 (delete ret-val elmo-pop3-connection-cache))
121 (elmo-pop3-open-connection
122 server user port auth
123 (elmo-get-passwd user-at-host) ssl))
125 (error "Connection failed"))
126 (setq buffer (car result))
127 (setq process (cdr result))
128 (when (and process (null buffer))
129 (elmo-remove-passwd user-at-host)
130 (delete-process process)
131 (error "Login failed")
133 (setq elmo-pop3-connection-cache
134 (append elmo-pop3-connection-cache
136 (cons user-at-host-on-port
137 (setq ret-val (list buffer process))))))
140 (defun elmo-pop3-send-command (buffer process command)
144 (goto-char (point-min))
145 (setq elmo-pop3-read-point (point))
146 (process-send-string process command)
147 (process-send-string process "\r\n")))
149 (defun elmo-pop3-send-command-no-erase (buffer process command)
153 (goto-char (point-min))
154 (setq elmo-pop3-read-point (point))
155 (process-send-string process command)
156 (process-send-string process "\r\n")))
158 (defun elmo-pop3-read-response (buffer process &optional not-command)
161 (let ((case-fold-search nil)
162 (response-string nil)
163 (response-continue t)
166 (while response-continue
167 (goto-char elmo-pop3-read-point)
168 (while (not (re-search-forward "\r?\n" nil t))
169 (accept-process-output process)
170 (goto-char elmo-pop3-read-point))
171 (setq match-end (point))
172 (setq response-string
173 (buffer-substring elmo-pop3-read-point (- match-end 2)))
174 (goto-char elmo-pop3-read-point)
175 (if (looking-at "\\+.*$")
177 (setq response-continue nil)
178 (setq elmo-pop3-read-point match-end)
181 (concat return-value "\n" response-string)
184 (if (looking-at "\\-.*$")
186 (setq response-continue nil)
187 (setq elmo-pop3-read-point match-end)
188 (setq return-value nil))
189 (setq elmo-pop3-read-point match-end)
191 (setq response-continue nil))
194 (concat return-value "\n" response-string)
196 (setq elmo-pop3-read-point match-end)))
199 (defun elmo-pop3-process-filter (process output)
201 (set-buffer (process-buffer process))
202 (goto-char (point-max))
205 (defun elmo-pop3-open-connection (server user port auth passphrase ssl)
208 process-buffer ret-val response capability)
212 (get-buffer-create (format " *POP session to %s:%d" host port)))
214 (set-buffer process-buffer)
215 (elmo-set-buffer-multibyte nil)
218 (elmo-open-network-stream "POP" process-buffer host port ssl))
219 (and (null process) (throw 'done nil))
220 (set-process-filter process 'elmo-pop3-process-filter)
221 ;; flush connections when exiting...
223 (set-buffer process-buffer)
224 (make-local-variable 'elmo-pop3-read-point)
225 (setq elmo-pop3-read-point (point-min))
226 (when (null (setq response
227 (elmo-pop3-read-response process-buffer process t)))
228 (setq ret-val (cons nil process))
230 (when (eq ssl 'starttls)
231 (elmo-pop3-send-command process-buffer process "stls")
232 (string-match "^\+OK"
233 (elmo-pop3-read-response
234 process-buffer process))
235 (starttls-negotiate process))
236 (cond ((string= auth "apop")
238 (if (string-match "^\+OK .*\\(<[^\>]+>\\)" response)
239 ;; good, APOP ready server
242 (elmo-pop3-send-command
243 process-buffer process
247 (concat (match-string 1 response)
249 ;; otherwise, fail (only APOP authentication)
250 (setq ret-val (cons nil process))
252 ((string= auth "cram-md5")
253 (elmo-pop3-send-command
254 process-buffer process "auth cram-md5")
255 (when (null (setq response
256 (elmo-pop3-read-response
257 process-buffer process t)))
258 (setq ret-val (cons nil process))
260 (elmo-pop3-send-command
261 process-buffer process
262 (elmo-base64-encode-string
263 (sasl-cram-md5 user passphrase
264 (elmo-base64-decode-string
265 (cadr (split-string response " ")))))))
266 ((string= auth "digest-md5")
267 (elmo-pop3-send-command
268 process-buffer process "auth digest-md5")
269 (when (null (setq response
270 (elmo-pop3-read-response
271 process-buffer process t)))
272 (setq ret-val (cons nil process))
274 (elmo-pop3-send-command
275 process-buffer process
276 (elmo-base64-encode-string
277 (sasl-digest-md5-digest-response
278 (elmo-base64-decode-string
279 (cadr (split-string response " ")))
280 user passphrase "pop" host)
282 (when (null (setq response
283 (elmo-pop3-read-response
284 process-buffer process t)))
285 (setq ret-val (cons nil process))
287 (elmo-pop3-send-command process-buffer process ""))
288 ((string= auth "scram-md5")
289 (let (server-msg-1 server-msg-2 client-msg-1 client-msg-2
291 (elmo-pop3-send-command
292 process-buffer process
293 (format "auth scram-md5 %s"
294 (elmo-base64-encode-string
296 (sasl-scram-md5-client-msg-1 user)))))
297 (when (null (setq response
298 (elmo-pop3-read-response
299 process-buffer process t)))
300 (setq ret-val (cons nil process))
303 (elmo-base64-decode-string
304 (cadr (split-string response " "))))
305 (elmo-pop3-send-command
306 process-buffer process
307 (elmo-base64-encode-string
308 (sasl-scram-md5-client-msg-2
312 (sasl-scram-md5-make-salted-pass
313 server-msg-1 passphrase)))))
314 (when (null (setq response
315 (elmo-pop3-read-response
316 process-buffer process t)))
317 (setq ret-val (cons nil process))
320 (elmo-base64-decode-string
321 (cadr (split-string response " "))))
322 (if (null (sasl-scram-md5-authenticate-server
328 (elmo-pop3-send-command
329 process-buffer process "") ))
332 (elmo-pop3-send-command process-buffer process
333 (format "user %s" user))
334 (when (null (elmo-pop3-read-response process-buffer process t))
335 (setq ret-val (cons nil process))
337 (elmo-pop3-send-command process-buffer process
338 (format "pass %s" passphrase))))
339 ;; read PASS or APOP response
340 (when (null (elmo-pop3-read-response process-buffer process t))
341 (setq ret-val (cons nil process))
343 (setq ret-val (cons process-buffer process)))))
346 (defun elmo-pop3-read-contents (buffer process)
349 (let ((case-fold-search nil)
351 (goto-char elmo-pop3-read-point)
352 (while (not (re-search-forward "^\\.\r\n" nil t))
353 (accept-process-output process)
354 (goto-char elmo-pop3-read-point))
355 (setq match-end (point))
357 (buffer-substring elmo-pop3-read-point
361 (defun elmo-pop3-list-folders (spec &optional hierarchy) nil)
362 (defun elmo-pop3-append-msg (spec string) nil nil)
363 (defun elmo-pop3-folder-creatable-p (spec) nil)
364 (defun elmo-pop3-create-folder (spec) nil)
366 (defun elmo-pop3-folder-exists-p (spec)
367 (if (and elmo-pop3-exists-exactly
368 (elmo-pop3-plugged-p spec))
370 (let (elmo-auto-change-plugged) ;;don't change plug status.
373 (elmo-pop3-get-connection spec)
374 (elmo-pop3-flush-connection))
378 (defun elmo-pop3-parse-list-response (string)
380 (let ((tmp-buffer (get-buffer-create " *ELMO PARSE TMP*"))
382 (set-buffer tmp-buffer)
383 (let ((case-fold-search t))
386 (goto-char (point-min))
387 (while (re-search-forward "^\\([0-9]*\\)[\t ].*$" nil t)
391 (elmo-match-buffer 1))
393 (kill-buffer tmp-buffer)
394 (nreverse ret-val)))))
396 (defun elmo-pop3-list-folder (spec)
398 (elmo-pop3-flush-connection)
399 (let* ((connection (elmo-pop3-get-connection spec))
400 (buffer (nth 0 connection))
401 (process (nth 1 connection))
402 response errmsg ret-val)
403 (elmo-pop3-send-command buffer process "list")
404 (if (null (elmo-pop3-read-response buffer process))
405 (error "POP List folder failed"))
406 (if (null (setq response (elmo-pop3-read-contents buffer process)))
407 (error "POP List folder failed"))
408 ;; POP server always returns a sequence of serial numbers.
409 (elmo-pop3-parse-list-response response))))
411 (defun elmo-pop3-max-of-folder (spec)
413 (elmo-pop3-flush-connection)
414 (let* ((connection (elmo-pop3-get-connection spec))
415 (buffer (nth 0 connection))
416 (process (nth 1 connection))
419 (elmo-pop3-send-command buffer process "STAT")
420 (setq response (elmo-pop3-read-response buffer process))
421 ;; response: "^\+OK 2 7570$"
422 (if (not (string-match "^\+OK[ \t]*\\([0-9]*\\)" response))
423 (error "POP STAT command failed")
426 (substring response (match-beginning 1)(match-end 1 ))))
427 (cons total total)))))
429 (defvar elmo-pop3-header-fetch-chop-length 200)
431 (defsubst elmo-pop3-next-result-arrived-p ()
433 ((eq (following-char) ?+)
434 (if (re-search-forward "\n\\.\r?\n" nil t)
438 (if (search-forward "\n" nil t)
444 (defun elmo-pop3-retrieve-headers (buffer tobuffer process articles)
448 (let ((number (length articles))
451 (last-point (point-min)))
452 ;; Send HEAD commands.
454 (elmo-pop3-send-command-no-erase
457 (format "top %s 0" (car articles))
459 ; (accept-process-output process 1)
460 (setq articles (cdr articles))
461 (setq count (1+ count))
462 ;; Every 200 requests we have to read the stream in
463 ;; order to avoid deadlocks.
464 (when (or elmo-pop3-send-command-synchronously
465 (null articles) ;All requests have been sent.
466 (zerop (% count elmo-pop3-header-fetch-chop-length)))
467 (unless elmo-pop3-send-command-synchronously
468 (accept-process-output process 1))
472 (goto-char last-point)
474 (while (elmo-pop3-next-result-arrived-p)
475 (setq last-point (point))
476 (setq received (1+ received)))
478 (and (zerop (% received 20))
479 (elmo-display-progress
480 'elmo-pop3-retrieve-headers "Getting headers..."
481 (/ (* received 100) number)))
482 (accept-process-output process 1)
483 ;(accept-process-output process)
486 (elmo-display-progress
487 'elmo-pop3-retrieve-headers "Getting headers..." 100)
488 ;; Remove all "\r"'s.
489 (goto-char (point-min))
490 (while (search-forward "\r\n" nil t)
491 (replace-match "\n"))
492 (copy-to-buffer tobuffer (point-min) (point-max))
493 ;(elmo-pop3-close-connection nil process buffer) ; close connection
496 (defalias 'elmo-pop3-msgdb-create 'elmo-pop3-msgdb-create-as-numlist)
497 (defun elmo-pop3-msgdb-create-as-numlist (spec numlist new-mark
498 already-mark seen-mark
499 important-mark seen-list)
501 (let* ((connection (elmo-pop3-get-connection spec))
502 (buffer (nth 0 connection))
503 (process (nth 1 connection))
504 response errmsg ret-val)
505 (elmo-pop3-msgdb-create-by-header buffer process numlist
506 new-mark already-mark
507 seen-mark seen-list))))
509 (defun elmo-pop3-msgdb-create-by-header (buffer process numlist
510 new-mark already-mark
513 (let ((tmp-buffer (get-buffer-create " *ELMO Overview TMP*"))
515 (elmo-pop3-retrieve-headers
516 buffer tmp-buffer process numlist)
518 (elmo-pop3-msgdb-create-message
522 new-mark already-mark seen-mark seen-list))
523 (kill-buffer tmp-buffer)
526 (defun elmo-pop3-msgdb-create-message (buffer
527 num numlist new-mark already-mark
532 overview number-alist mark-alist
533 entity i number message-id gmark seen)
535 (elmo-set-buffer-multibyte default-enable-multibyte-characters)
536 (goto-char (point-min))
538 (message "Creating msgdb...")
540 (setq beg (save-excursion (forward-line 1) (point)))
541 (elmo-pop3-next-result-arrived-p)
545 (narrow-to-region beg (point))
547 (elmo-msgdb-create-overview-from-buffer
549 (setq numlist (cdr numlist))
552 (elmo-msgdb-append-element
555 (elmo-msgdb-number-add number-alist
556 (elmo-msgdb-overview-entity-get-number entity)
558 (setq message-id (car entity))
559 (setq seen (member message-id seen-list))
560 (if (setq gmark (or (elmo-msgdb-global-mark-get message-id)
561 (if (elmo-cache-exists-p
567 (if elmo-pop3-use-cache
571 (elmo-msgdb-mark-append
573 (elmo-msgdb-overview-entity-get-number entity)
577 (and (zerop (% i 20))
578 (elmo-display-progress
579 'elmo-pop3-msgdb-create-message "Creating msgdb..."
582 (elmo-display-progress
583 'elmo-pop3-msgdb-create-message "Creating msgdb..." 100)
584 (list overview number-alist mark-alist))))
586 (defun elmo-pop3-read-body (buffer process outbuf)
587 (with-current-buffer buffer
588 (let ((start elmo-pop3-read-point)
591 (while (not (re-search-forward "^\\.\r?\n" nil t))
592 (accept-process-output process)
595 (with-current-buffer outbuf
597 (insert-buffer-substring buffer start (- end 3))
598 (elmo-delete-cr-get-content-type)))))
600 (defun elmo-pop3-read-msg (spec number outbuf)
602 (let* ((connection (elmo-pop3-get-connection spec))
603 (buffer (car connection))
604 (process (cadr connection))
605 (cwf (caddr connection))
607 (elmo-pop3-send-command buffer process
608 (format "retr %s" number))
609 (when (null (setq response (elmo-pop3-read-response
611 (error "Fetching message failed"))
612 (setq response (elmo-pop3-read-body buffer process outbuf))
614 (goto-char (point-min))
615 (while (re-search-forward "^\\." nil t)
620 (defun elmo-pop3-delete-msg (buffer process number)
621 (let (response errmsg msg)
622 (elmo-pop3-send-command buffer process
623 (format "dele %s" number))
624 (when (null (setq response (elmo-pop3-read-response
626 (error "Deleting message failed"))))
628 (defun elmo-pop3-delete-msgs (spec msgs)
630 (let* ((connection (elmo-pop3-get-connection spec))
631 (buffer (car connection))
632 (process (cadr connection)))
633 (mapcar '(lambda (msg) (elmo-pop3-delete-msg
637 (defun elmo-pop3-search (spec condition &optional numlist)
638 (error "Searching in pop3 folder is not implemented yet"))
640 (defun elmo-pop3-use-cache-p (spec number)
643 (defun elmo-pop3-local-file-p (spec number)
646 (defun elmo-pop3-port-label (spec)
648 (if (elmo-pop3-spec-ssl spec) "!ssl" "")))
650 (defsubst elmo-pop3-portinfo (spec)
651 (list (elmo-pop3-spec-hostname spec)
652 (elmo-pop3-spec-port spec)))
654 (defun elmo-pop3-plugged-p (spec)
655 (apply 'elmo-plugged-p
656 (append (elmo-pop3-portinfo spec)
657 (list nil (quote (elmo-pop3-port-label spec))))))
659 (defun elmo-pop3-set-plugged (spec plugged add)
660 (apply 'elmo-set-plugged plugged
661 (append (elmo-pop3-portinfo spec)
662 (list nil nil (quote (elmo-pop3-port-label spec)) add))))
664 (defalias 'elmo-pop3-sync-number-alist
665 'elmo-generic-sync-number-alist)
666 (defalias 'elmo-pop3-list-folder-unread
667 'elmo-generic-list-folder-unread)
668 (defalias 'elmo-pop3-list-folder-important
669 'elmo-generic-list-folder-important)
670 (defalias 'elmo-pop3-commit 'elmo-generic-commit)
674 ;;; elmo-pop3.el ends here