df30cb17bc71ddd58d6720d3c55b9ad03c81e202
[elisp/wanderlust.git] / elmo / elmo-net.el
1 ;;; elmo-net.el --- Network module for ELMO.
2
3 ;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
4
5 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Keywords: mail, net news
7
8 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
9
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)
13 ;; any later version.
14 ;;
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.
19 ;;
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.
24 ;;
25
26 ;;; Commentary:
27 ;;
28
29 (eval-when-compile (require 'cl))
30
31 (require 'elmo-util)
32 (require 'elmo-dop)
33 (require 'elmo-vars)
34 (require 'elmo-cache)
35 (require 'elmo)
36
37 ;;; Code:
38 ;;
39
40 (defconst elmo-net-folder-name-syntax '((?@ [server ".+"])
41                                         (?: [port "^[0-9]+$"])
42                                         (?! stream-type)))
43
44 ;;; ELMO net folder
45 (eval-and-compile
46   (luna-define-class elmo-net-folder
47                      (elmo-folder)
48                      (user auth server port stream-type))
49   (luna-define-internal-accessors 'elmo-net-folder))
50
51 ;;; Session
52 (eval-and-compile
53   (autoload 'starttls-negotiate "starttls")
54   (autoload 'sasl-find-mechanism "sasl")
55   (autoload 'sasl-make-client "sasl")
56   (autoload 'sasl-mechanism-name "sasl")
57   (autoload 'sasl-next-step "sasl")
58   (autoload 'sasl-step-data "sasl")
59   (autoload 'sasl-step-set-data "sasl"))
60
61 (defvar sasl-mechanisms)
62
63 (defcustom elmo-network-session-idle-timeout nil
64   "Idle timeout of the network cache. Specified in seconds.
65 If elapsed time since last access is larger than this value,
66 cached session is not reused.
67 If nil, network cache is reused."
68   :type '(choice number (const nil))
69   :group 'elmo)
70
71 (defcustom elmo-network-session-retry-count nil
72   "Retry count for authentication when open network session.
73 If nil, just once. If t, until success."
74   :type '(choice (integer :tag "Times")
75                  (const :tag "Just once" nil)
76                  (const :tag "Until success" t))
77   :group 'elmo)
78
79 ;;; Code:
80 ;;
81 (eval-and-compile
82   (luna-define-class elmo-network-session () (name
83                                               server
84                                               port
85                                               user
86                                               auth
87                                               stream-type
88                                               process
89                                               greeting
90                                               last-accessed))
91   (luna-define-internal-accessors 'elmo-network-session))
92
93 (luna-define-generic elmo-network-initialize-session (session)
94   "Initialize SESSION (Called before authentication).")
95
96 (luna-define-generic elmo-network-initialize-session-buffer (session buffer)
97   "Initialize SESSION's BUFFER.")
98
99 (luna-define-generic elmo-network-authenticate-session (session)
100   "Authenticate SESSION.")
101
102 (luna-define-generic elmo-network-setup-session (session)
103   "Setup SESSION. (Called after authentication).")
104
105 (luna-define-generic elmo-network-close-session (session)
106   "Close SESSION.")
107
108 (luna-define-method
109   elmo-network-initialize-session-buffer ((session
110                                            elmo-network-session) buffer)
111   (with-current-buffer buffer
112     (set-buffer-multibyte nil)
113     (buffer-disable-undo (current-buffer))))
114
115 (luna-define-method elmo-network-close-session ((session elmo-network-session))
116   (when (elmo-network-session-process-internal session)
117 ;;; (memq (process-status (elmo-network-session-process-internal session))
118 ;;;       '(open run))
119     (kill-buffer (process-buffer
120                   (elmo-network-session-process-internal session)))
121     (delete-process (elmo-network-session-process-internal session))))
122
123 (defmacro elmo-network-stream-type-spec-string (stream-type)
124   `(nth 0 ,stream-type))
125
126 (defmacro elmo-network-stream-type-symbol (stream-type)
127   `(nth 1 ,stream-type))
128
129 (defmacro elmo-network-stream-type-feature (stream-type)
130   `(nth 2 ,stream-type))
131
132 (defmacro elmo-network-stream-type-function (stream-type)
133   `(nth 3 ,stream-type))
134
135 (defsubst elmo-network-session-password-key (session)
136   (format "%s:%s/%s@%s:%d"
137           (upcase
138            (nth 1 (split-string (symbol-name
139                                  (luna-class-name session)) "[4-]")))
140           (elmo-network-session-user-internal session)
141           (elmo-network-session-auth-internal session)
142           (elmo-network-session-server-internal session)
143           (elmo-network-session-port-internal session)))
144
145 (defvar elmo-network-session-cache nil)
146
147 (defsubst elmo-network-session-cache-key (name folder)
148   "Returns session cache key for NAME and FOLDER."
149   (format "%s:%s/%s@%s:%d%s"
150           name
151           (elmo-net-folder-user-internal folder)
152           (elmo-net-folder-auth-internal folder)
153           (elmo-net-folder-server-internal folder)
154           (elmo-net-folder-port-internal folder)
155           (or
156            (elmo-network-stream-type-spec-string
157             (elmo-net-folder-stream-type-internal folder)) "")))
158
159 (defun elmo-network-clear-session-cache ()
160   "Clear session cache."
161   (interactive)
162   (dolist (pair elmo-network-session-cache)
163     (elmo-network-close-session (cdr pair)))
164   (setq elmo-network-session-cache nil))
165
166 (defsubst elmo-network-session-buffer-name (session)
167   (format " *%s session for %s@%s:%d%s"
168           (elmo-network-session-name-internal session)
169           (elmo-network-session-user-internal session)
170           (elmo-network-session-server-internal session)
171           (elmo-network-session-port-internal session)
172           (or (elmo-network-stream-type-spec-string
173                (elmo-network-session-stream-type-internal session))
174               "")))
175
176 (defmacro elmo-network-session-buffer (session)
177   "Get buffer for SESSION."
178   `(process-buffer (elmo-network-session-process-internal ,session)))
179
180 (defun elmo-network-get-session (class name folder &optional if-exists)
181   "Get network session from session cache or a new network session.
182 CLASS is the class name of the session.
183 NAME is the name of the process.
184 FOLDER is the ELMO folder structure.
185 Returns a `elmo-network-session' instance.
186 If optional argument IF-EXISTS is non-nil, it does not return session
187 if there is no session cache.
188 if making session failed, returns nil."
189   (let (pair session key)
190     (if (not (elmo-plugged-p
191               (elmo-net-folder-server-internal folder)
192               (elmo-net-folder-port-internal folder)
193               (elmo-network-stream-type-symbol
194                (elmo-net-folder-stream-type-internal folder))))
195         (error "Unplugged"))
196     (setq pair (assoc (setq key (elmo-network-session-cache-key name folder))
197                       elmo-network-session-cache))
198     (when (and pair
199                (or (not (memq (process-status
200                                (elmo-network-session-process-internal
201                                 (cdr pair)))
202                               '(open run)))
203                    (and elmo-network-session-idle-timeout
204                         (elmo-network-session-last-accessed-internal
205                          (cdr pair))
206                         (elmo-time-expire
207                          (elmo-network-session-last-accessed-internal
208                           (cdr pair))
209                          elmo-network-session-idle-timeout))))
210       (setq elmo-network-session-cache
211             (delq pair elmo-network-session-cache))
212       (elmo-network-close-session (cdr pair))
213       (setq pair nil))
214     (if pair
215         (progn
216           (elmo-network-session-set-last-accessed-internal
217            (cdr pair) (current-time))
218           (cdr pair))                   ; connection cache exists.
219       (unless if-exists
220         (setq session
221               (elmo-network-open-session
222                class
223                name
224                (elmo-net-folder-server-internal folder)
225                (elmo-net-folder-port-internal folder)
226                (elmo-net-folder-user-internal folder)
227                (elmo-net-folder-auth-internal folder)
228                (elmo-net-folder-stream-type-internal folder)))
229         (setq elmo-network-session-cache
230               (cons (cons key session)
231                     elmo-network-session-cache))
232         session))))
233
234 (defun elmo-network-session-buffer-create (session)
235   (let ((buffer-name (elmo-network-session-buffer-name session))
236         buffer)
237     (when (get-buffer buffer-name)
238       (kill-buffer buffer-name))
239     (setq buffer (get-buffer-create buffer-name))
240     (elmo-network-initialize-session-buffer session buffer)
241     buffer))
242
243 (defun elmo-network-open-session (class name server port user auth
244                                         stream-type)
245   "Open an authenticated network session.
246 CLASS is the class name of the session.
247 NAME is the name of the process.
248 SERVER is the name of the server server.
249 PORT is the port number of the service.
250 USER is the user-id for the authenticate.
251 AUTH is the authenticate method name (symbol).
252 STREAM-TYPE is the stream type (See also `elmo-network-stream-type-alist').
253 Returns a process object.  if making session failed, returns nil."
254   (let ((session
255          (luna-make-entity class
256                            :name name
257                            :server server
258                            :port port
259                            :user user
260                            :auth auth
261                            :stream-type stream-type
262                            :process nil
263                            :greeting nil
264                            :last-accessed (current-time)))
265         (retry elmo-network-session-retry-count)
266         success)
267     (while (not success)
268       (condition-case error
269           (when (elmo-network-session-set-process-internal
270                  session
271                  (elmo-open-network-stream
272                   (elmo-network-session-name-internal session)
273                   (elmo-network-session-buffer-create session)
274                   server port stream-type))
275             (elmo-network-initialize-session session)
276             (elmo-network-authenticate-session session)
277             (elmo-network-setup-session session)
278             (setq success t))
279         (elmo-authenticate-error
280          (elmo-remove-passwd (elmo-network-session-password-key session))
281          (message "Authetication is failed")
282          (sit-for 1)
283          (elmo-network-close-session session)
284          (unless (if (numberp retry)
285                      (> (setq retry (1- retry)) 0)
286                    retry)
287            (signal (car error) (cdr error))))
288         (elmo-open-error
289          (elmo-set-plugged nil server port
290                            (elmo-network-stream-type-symbol stream-type)
291                            (current-time))
292          (message "Auto plugged off at %s:%d :%s" server port (cadr error))
293          (sit-for 1)
294          (elmo-network-close-session session)
295          (signal (car error) (cdr error)))
296         (error
297          (elmo-network-close-session session)
298          (signal (car error) (cdr error)))))
299     session))
300
301 (defun elmo-open-network-stream (name buffer server service stream-type)
302   (let ((auto-plugged (and elmo-auto-change-plugged
303                            (> elmo-auto-change-plugged 0)))
304         process)
305     (if (and stream-type
306              (elmo-network-stream-type-feature stream-type))
307         (require (elmo-network-stream-type-feature stream-type)))
308     (condition-case err
309         (let (process-connection-type)
310           (as-binary-process
311            (setq process
312                  (if stream-type
313                      (funcall (elmo-network-stream-type-function stream-type)
314                               name buffer server service)
315                    (open-network-stream name buffer server service)))))
316       (error
317        (when auto-plugged
318          (elmo-set-plugged nil server service
319                            (elmo-network-stream-type-symbol stream-type)
320                            (current-time))
321          (message "Auto plugged off at %s:%d" server service)
322          (sit-for 1))
323        (signal (car err) (cdr err))))
324     (when process
325       (process-kill-without-query process)
326       (when auto-plugged
327         (elmo-set-plugged t server service
328                           (elmo-network-stream-type-symbol stream-type)))
329       process)))
330
331 (defun elmo-get-network-stream-type (symbol)
332   "Return network stream type corresponding to SYMBOL.
333 Returned value is searched from `elmo-network-stream-type-alist'."
334   (let ((alist elmo-network-stream-type-alist)
335         spec)
336     (while alist
337       (when (eq (nth 1 (car alist)) symbol)
338         (setq spec (car alist))
339         (setq alist nil))
340       (setq alist (cdr alist)))
341     spec))
342
343 (defun elmo-net-folder-set-parameters (folder params &optional defaults)
344   (let ((port (cdr (assq 'port params)))
345         (stream-type (cdr (assq 'stream-type params))))
346     ;; server
347     (elmo-net-folder-set-server-internal
348      folder
349      (or (cdr (assq 'server params))
350          (plist-get defaults :server)))
351     ;; port
352     (elmo-net-folder-set-port-internal
353      folder
354      (or (and port (string-to-int port))
355          (plist-get defaults :port)))
356     ;; stream-type
357     (elmo-net-folder-set-stream-type-internal
358      folder
359      (or (and stream-type
360               (assoc (concat "!" stream-type) elmo-network-stream-type-alist))
361          (plist-get defaults :stream-type)))))
362
363 (luna-define-method elmo-folder-initialize ((folder elmo-net-folder) name)
364   ;; user and auth should be set in subclass.
365   (when (string-match "\\(@[^@:/!]+\\)?\\(:[0-9]+\\)?\\(!.*\\)?$" name)
366     (elmo-net-folder-set-parameters
367      folder
368      (car (elmo-parse-separated-tokens
369            (substring name (match-beginning 0))
370            elmo-net-folder-name-syntax))))
371   folder)
372
373 (luna-define-method elmo-net-port-info ((folder elmo-net-folder))
374   (list (elmo-net-folder-server-internal folder)
375         (elmo-net-folder-port-internal folder)
376         (elmo-network-stream-type-symbol
377          (elmo-net-folder-stream-type-internal folder))))
378
379 (defun elmo-net-port-label (folder)
380   (concat
381    (symbol-name (elmo-folder-type-internal folder))
382    (if (elmo-net-folder-stream-type-internal folder)
383        (concat "!" (symbol-name
384                     (elmo-network-stream-type-symbol
385                      (elmo-net-folder-stream-type-internal
386                       folder)))))))
387
388 (luna-define-method elmo-folder-plugged-p ((folder elmo-net-folder))
389   (apply 'elmo-plugged-p
390          (append (elmo-net-port-info folder)
391                  (list nil (quote (elmo-net-port-label folder))))))
392
393 (luna-define-method elmo-folder-set-plugged ((folder elmo-net-folder)
394                                              plugged &optional add)
395   (apply 'elmo-set-plugged plugged
396          (append (elmo-net-port-info folder)
397                  (list nil nil (quote (elmo-net-port-label folder)) add))))
398
399 (luna-define-method elmo-folder-create ((folder elmo-net-folder))
400   (if (elmo-folder-plugged-p folder)
401       (elmo-folder-send folder 'elmo-folder-create-plugged)
402     (elmo-folder-send folder 'elmo-folder-create-unplugged)))
403
404 (luna-define-method elmo-folder-create-unplugged ((folder elmo-net-folder))
405   (if elmo-enable-disconnected-operation
406       (elmo-folder-create-dop folder)
407     (error "Unplugged")))
408
409 (luna-define-method elmo-folder-exists-p ((folder elmo-net-folder))
410   (if (elmo-folder-plugged-p folder)
411       (elmo-folder-send folder 'elmo-folder-exists-p-plugged)
412     ;; If unplugged, guess by msgdb.
413     (file-directory-p (elmo-folder-msgdb-path folder))))
414
415 (luna-define-method elmo-folder-status ((folder elmo-net-folder))
416   (if (elmo-folder-plugged-p folder)
417       (elmo-folder-send folder 'elmo-folder-status-plugged)
418     (elmo-folder-send folder 'elmo-folder-status-unplugged)))
419
420 (luna-define-method elmo-folder-status-unplugged
421   ((folder elmo-net-folder))
422   (if elmo-enable-disconnected-operation
423       (elmo-folder-status-dop folder)
424     (error "Unplugged")))
425
426 (luna-define-method elmo-folder-next-message-number ((folder elmo-net-folder))
427   (if (elmo-folder-plugged-p folder)
428       (elmo-folder-send folder 'elmo-folder-next-message-number-plugged)
429     (elmo-folder-send folder 'elmo-folder-next-message-number-unplugged)))
430
431 (luna-define-method elmo-folder-next-message-number-unplugged
432   ((folder elmo-net-folder))
433   (if elmo-enable-disconnected-operation
434       (elmo-folder-next-message-number-dop folder)
435     (error "Unplugged")))
436
437 (luna-define-method elmo-folder-list-messages-internal
438   ((folder elmo-net-folder) &optional nohide)
439   (elmo-net-folder-list-messages-internal folder nohide))
440
441 (defun elmo-net-folder-list-messages-internal (folder nohide)
442   (if (elmo-folder-plugged-p folder)
443       (elmo-folder-send folder 'elmo-folder-list-messages-plugged nohide)
444     (elmo-folder-send folder 'elmo-folder-list-messages-unplugged)))
445
446 (luna-define-method elmo-folder-list-messages-plugged
447   ((folder elmo-net-folder))
448   nil)
449
450 ;; Should consider offline append and removal.
451 (luna-define-method elmo-folder-list-messages-unplugged ((folder
452                                                           elmo-net-folder))
453   (if elmo-enable-disconnected-operation
454       (let ((deleting (elmo-dop-list-deleting-messages folder)))
455         (nconc
456          ;; delete deleting messages
457          (elmo-delete-if
458           (lambda (number) (memq number deleting))
459           ;; current number-list.
460           (elmo-folder-list-messages folder nil 'in-msgdb))
461          ;; append appending messages
462          (mapcar (lambda (x) (* -1 x))
463                  (elmo-dop-spool-folder-list-messages folder))))
464     t))
465
466 (luna-define-method elmo-folder-list-flagged-internal ((folder elmo-net-folder)
467                                                        flag)
468   (if (and (elmo-folder-plugged-p folder)
469            (elmo-folder-use-flag-p folder))
470       (elmo-folder-send folder 'elmo-folder-list-flagged-plugged flag)
471     ;; Should consider offline append and removal?
472     t))
473
474 (luna-define-method elmo-folder-list-flagged-plugged ((folder elmo-net-folder)
475                                                       flag)
476   t)
477
478 (luna-define-method elmo-folder-delete-messages-internal ((folder
479                                                            elmo-net-folder)
480                                                           numbers)
481    (if (elmo-folder-plugged-p folder)
482        (elmo-folder-send folder 'elmo-folder-delete-messages-plugged numbers)
483      (elmo-folder-send folder 'elmo-folder-delete-messages-unplugged numbers)))
484
485 (luna-define-method elmo-folder-delete-messages-unplugged ((folder
486                                                             elmo-net-folder)
487                                                            numbers)
488   (elmo-folder-delete-messages-dop folder numbers))
489
490 (luna-define-method elmo-folder-msgdb-create ((folder elmo-net-folder)
491                                               numbers flag-table)
492   (if (elmo-folder-plugged-p folder)
493       (elmo-folder-send folder 'elmo-folder-msgdb-create-plugged
494                         numbers flag-table)
495     (elmo-folder-send folder 'elmo-folder-msgdb-create-unplugged
496                       numbers flag-table)))
497
498 (luna-define-method elmo-folder-msgdb-create-unplugged ((folder
499                                                          elmo-net-folder)
500                                                         numbers
501                                                         flag-table)
502   ;; XXXX should be appended to already existing msgdb.
503   (elmo-dop-msgdb
504    (elmo-folder-msgdb-create (elmo-dop-spool-folder folder)
505                              (mapcar 'abs numbers)
506                              flag-table)))
507
508 (luna-define-method elmo-folder-set-flag :before ((folder elmo-net-folder)
509                                                   numbers
510                                                   flag
511                                                   &optional is-local)
512   (when (and (not is-local)
513              (elmo-folder-use-flag-p folder))
514     (elmo-folder-send folder
515                       (if (elmo-folder-plugged-p folder)
516                           'elmo-folder-set-flag-plugged
517                         'elmo-folder-set-flag-unplugged)
518                       numbers
519                       flag)))
520
521 (luna-define-method elmo-folder-unset-flag :before ((folder elmo-net-folder)
522                                                     numbers
523                                                     flag
524                                                     &optional is-local)
525   (when (and (not is-local)
526              (elmo-folder-use-flag-p folder))
527     (elmo-folder-send folder
528                       (if (elmo-folder-plugged-p folder)
529                           'elmo-folder-unset-flag-plugged
530                         'elmo-folder-unset-flag-unplugged)
531                       numbers
532                       flag)))
533
534 (luna-define-method elmo-folder-set-flag-unplugged ((folder elmo-net-folder)
535                                                     numbers flag)
536   (elmo-folder-set-flag-dop folder numbers flag))
537
538 (luna-define-method elmo-folder-unset-flag-unplugged ((folder elmo-net-folder)
539                                                       numbers flag)
540   (elmo-folder-unset-flag-dop folder numbers flag))
541
542 (luna-define-method elmo-message-encache :around ((folder elmo-net-folder)
543                                                   number &optional read)
544   (if (elmo-folder-plugged-p folder)
545       (luna-call-next-method)
546     (if elmo-enable-disconnected-operation
547         (elmo-message-encache-dop folder number read)
548       (error "Unplugged"))))
549
550 (luna-define-generic elmo-message-fetch-plugged (folder number strategy
551                                                         &optional
552                                                         section
553                                                         outbuf
554                                                         unseen)
555   "")
556
557 (luna-define-generic elmo-message-fetch-unplugged (folder number strategy
558                                                           &optional
559                                                           section
560                                                           outbuf
561                                                           unseen)
562   "")
563
564 (luna-define-method elmo-message-fetch-internal ((folder elmo-net-folder)
565                                                  number strategy
566                                                  &optional section unseen)
567   (if (elmo-folder-plugged-p folder)
568       (elmo-message-fetch-plugged folder number
569                                   strategy section
570                                   (current-buffer) unseen)
571     (elmo-message-fetch-unplugged folder number
572                                   strategy section
573                                   (current-buffer) unseen)))
574
575 (luna-define-method elmo-message-fetch-unplugged
576   ((folder elmo-net-folder) number strategy  &optional section outbuf unseen)
577   (if (and elmo-enable-disconnected-operation
578            (< number 0))
579       (elmo-message-fetch-internal
580        (elmo-dop-spool-folder folder) (abs number) strategy
581        section unseen)
582     (error "Unplugged")))
583
584 (luna-define-method elmo-folder-check ((folder elmo-net-folder))
585   (if (elmo-folder-plugged-p folder)
586       (elmo-folder-send folder 'elmo-folder-check-plugged)))
587
588 (luna-define-method elmo-folder-close :after ((folder elmo-net-folder))
589   (if (elmo-folder-plugged-p folder)
590       (elmo-folder-send folder 'elmo-folder-check-plugged)))
591
592 (luna-define-method elmo-folder-diff :around ((folder elmo-net-folder))
593   (if (and (elmo-folder-use-flag-p folder)
594            (elmo-folder-plugged-p folder))
595       (elmo-folder-send folder 'elmo-folder-diff-plugged)
596     (luna-call-next-method)))
597
598 (luna-define-method elmo-folder-local-p ((folder elmo-net-folder))
599   nil)
600
601 (luna-define-method elmo-quit ((folder elmo-net-folder))
602   (elmo-network-clear-session-cache))
603
604 (require 'product)
605 (product-provide (provide 'elmo-net) (require 'elmo-version))
606
607 ;;; elmo-net.el ends here