* spam-stat.el (spam-stat-score-buffer): Simplify mapcar usage.
[gnus] / lisp / sieve-manage.el
1 ;;; sieve-manage.el --- Implementation of the managesive protocol in elisp
2 ;; Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc.
3
4 ;; Author: Simon Josefsson <simon@josefsson.org>
5
6 ;; This file is part of GNU Emacs.
7
8 ;; GNU Emacs is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation; either version 2, or (at your option)
11 ;; any later version.
12
13 ;; GNU Emacs is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 ;; GNU General Public License for more details.
17
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
20 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 ;; Boston, MA 02111-1307, USA.
22
23 ;;; Commentary:
24
25 ;; This library provides an elisp API for the managesieve network
26 ;; protocol.
27 ;;
28 ;; It uses the SASL library for authentication, which means it
29 ;; supports DIGEST-MD5, CRAM-MD5, SCRAM-MD5, NTLM, PLAIN and LOGIN
30 ;; methods.  STARTTLS is not well tested, but should be easy to get to
31 ;; work if someone wants.
32 ;;
33 ;; The API should be fairly obvious for anyone familiar with the
34 ;; managesieve protocol, interface functions include:
35 ;;
36 ;; `sieve-manage-open'
37 ;; open connection to managesieve server, returning a buffer to be
38 ;; used by all other API functions.
39 ;;
40 ;; `sieve-manage-opened'
41 ;; check if a server is open or not
42 ;;
43 ;; `sieve-manage-close'
44 ;; close a server connection.
45 ;;
46 ;; `sieve-manage-authenticate'
47 ;; `sieve-manage-listscripts'
48 ;; `sieve-manage-deletescript'
49 ;; `sieve-manage-getscript'
50 ;; performs managesieve protocol actions
51 ;;
52 ;; and that's it.  Example of a managesieve session in *scratch*:
53 ;;
54 ;; (setq my-buf (sieve-manage-open "my.server.com"))
55 ;; " *sieve* my.server.com:2000*"
56 ;;
57 ;; (sieve-manage-authenticate "myusername" "mypassword" my-buf)
58 ;; 'auth
59 ;;
60 ;; (sieve-manage-listscripts my-buf)
61 ;; ("vacation" "testscript" ("splitmail") "badscript")
62 ;;
63 ;; References:
64 ;;
65 ;; draft-martin-managesieve-02.txt,
66 ;; "A Protocol for Remotely Managing Sieve Scripts",
67 ;; by Tim Martin.
68 ;;
69 ;; Release history:
70 ;;
71 ;; 2001-10-31 Committed to Oort Gnus.
72 ;; 2002-07-27 Added DELETESCRIPT.  Suggested by Ned Ludd.
73 ;; 2002-08-03 Use SASL library.
74
75 ;;; Code:
76
77 (require 'password)
78 (eval-when-compile
79   (require 'sasl)
80   (require 'starttls))
81 (eval-and-compile
82   (autoload 'sasl-find-mechanism "sasl")
83   (autoload 'starttls-open-stream "starttls"))
84
85 ;; User customizable variables:
86
87 (defgroup sieve-manage nil
88   "Low-level Managesieve protocol issues."
89   :group 'mail
90   :prefix "sieve-")
91
92 (defcustom sieve-manage-log "*sieve-manage-log*"
93   "Name of buffer for managesieve session trace."
94   :type 'string)
95
96 (defcustom sieve-manage-default-user (user-login-name)
97   "Default username to use."
98   :type 'string)
99
100 (defcustom sieve-manage-server-eol "\r\n"
101   "The EOL string sent from the server."
102   :type 'string)
103
104 (defcustom sieve-manage-client-eol "\r\n"
105   "The EOL string we send to the server."
106   :type 'string)
107
108 (defcustom sieve-manage-streams '(network starttls shell)
109   "Priority of streams to consider when opening connection to server.")
110
111 (defcustom sieve-manage-stream-alist
112   '((network   sieve-manage-network-p          sieve-manage-network-open)
113     (shell     sieve-manage-shell-p            sieve-manage-shell-open)
114     (starttls  sieve-manage-starttls-p         sieve-manage-starttls-open))
115   "Definition of network streams.
116
117 \(NAME CHECK OPEN)
118
119 NAME names the stream, CHECK is a function returning non-nil if the
120 server support the stream and OPEN is a function for opening the
121 stream.")
122
123 (defcustom sieve-manage-authenticators '(digest-md5
124                                          cram-md5
125                                          scram-md5
126                                          ntlm
127                                          plain
128                                          login)
129   "Priority of authenticators to consider when authenticating to server.")
130
131 (defcustom sieve-manage-authenticator-alist
132   '((cram-md5   sieve-manage-cram-md5-p       sieve-manage-cram-md5-auth)
133     (digest-md5 sieve-manage-digest-md5-p     sieve-manage-digest-md5-auth)
134     (scram-md5  sieve-manage-scram-md5-p      sieve-manage-scram-md5-auth)
135     (ntlm       sieve-manage-ntlm-p           sieve-manage-ntlm-auth)
136     (plain      sieve-manage-plain-p          sieve-manage-plain-auth)
137     (login      sieve-manage-login-p          sieve-manage-login-auth))
138   "Definition of authenticators.
139
140 \(NAME CHECK AUTHENTICATE)
141
142 NAME names the authenticator.  CHECK is a function returning non-nil if
143 the server support the authenticator and AUTHENTICATE is a function
144 for doing the actual authentication.")
145
146 (defcustom sieve-manage-default-port 2000
147   "Default port number for managesieve protocol."
148   :type 'integer)
149
150 ;; Internal variables:
151
152 (defconst sieve-manage-local-variables '(sieve-manage-server
153                                          sieve-manage-port
154                                          sieve-manage-auth
155                                          sieve-manage-stream
156                                          sieve-manage-username
157                                          sieve-manage-password
158                                          sieve-manage-process
159                                          sieve-manage-client-eol
160                                          sieve-manage-server-eol
161                                          sieve-manage-capability))
162 (defconst sieve-manage-default-stream 'network)
163 (defconst sieve-manage-coding-system-for-read 'binary)
164 (defconst sieve-manage-coding-system-for-write 'binary)
165 (defvar sieve-manage-stream nil)
166 (defvar sieve-manage-auth nil)
167 (defvar sieve-manage-server nil)
168 (defvar sieve-manage-port nil)
169 (defvar sieve-manage-username nil)
170 (defvar sieve-manage-password nil)
171 (defvar sieve-manage-state 'closed
172   "Managesieve state.
173 Valid states are `closed', `initial', `nonauth', and `auth'.")
174 (defvar sieve-manage-process nil)
175 (defvar sieve-manage-capability nil)
176
177 ;; Internal utility functions
178
179 (defsubst sieve-manage-disable-multibyte ()
180   "Enable multibyte in the current buffer."
181   (when (fboundp 'set-buffer-multibyte)
182     (set-buffer-multibyte nil)))
183
184 ;; Uses the dynamically bound `reason' variable.
185 (defvar reason)
186 (defun sieve-manage-interactive-login (buffer loginfunc)
187   "Login to server in BUFFER.
188 LOGINFUNC is passed a username and a password, it should return t if
189 it where sucessful authenticating itself to the server, nil otherwise.
190 Returns t if login was successful, nil otherwise."
191   (with-current-buffer buffer
192     (make-variable-buffer-local 'sieve-manage-username)
193     (make-variable-buffer-local 'sieve-manage-password)
194     (let (user passwd ret reason passwd-key)
195       (condition-case ()
196           (while (or (not user) (not passwd))
197             (setq user (or sieve-manage-username
198                            (read-from-minibuffer
199                             (concat "Managesieve username for "
200                                     sieve-manage-server ": ")
201                             (or user sieve-manage-default-user)))
202                   passwd-key (concat "managesieve:" user "@" sieve-manage-server
203                                      ":" sieve-manage-port)
204                   passwd (or sieve-manage-password
205                              (password-read (concat "Managesieve password for "
206                                                     user "@" sieve-manage-server
207                                                     ": ")
208                                             passwd-key)))
209             (when (y-or-n-p "Store password for this session? ")
210               (password-cache-add passwd-key (copy-sequence passwd)))
211             (when (and user passwd)
212               (if (funcall loginfunc user passwd)
213                   (setq ret t
214                         sieve-manage-username user)
215                 (if reason
216                     (message "Login failed (reason given: %s)..." reason)
217                   (message "Login failed..."))
218                 (password-cache-remove passwd-key)
219                 (setq sieve-manage-password nil)
220                 (setq passwd nil)
221                 (setq reason nil)
222                 (sit-for 1))))
223         (quit (with-current-buffer buffer
224                 (password-cache-remove passwd-key)
225                 (setq user nil
226                       passwd nil
227                       sieve-manage-password nil)))
228         (error (with-current-buffer buffer
229                  (password-cache-remove passwd-key)
230                  (setq user nil
231                        passwd nil
232                        sieve-manage-password nil))))
233       ret)))
234
235 (defun sieve-manage-erase (&optional p buffer)
236   (let ((buffer (or buffer (current-buffer))))
237     (and sieve-manage-log
238          (with-current-buffer (get-buffer-create sieve-manage-log)
239            (sieve-manage-disable-multibyte)
240            (buffer-disable-undo)
241            (goto-char (point-max))
242            (insert-buffer-substring buffer (with-current-buffer buffer
243                                              (point-min))
244                                     (or p (with-current-buffer buffer
245                                             (point-max)))))))
246   (delete-region (point-min) (or p (point-max))))
247
248 (defun sieve-manage-open-1 (buffer)
249   (with-current-buffer buffer
250     (sieve-manage-erase)
251     (setq sieve-manage-state 'initial
252           sieve-manage-process
253           (condition-case ()
254               (funcall (nth 2 (assq sieve-manage-stream
255                                     sieve-manage-stream-alist))
256                        "sieve" buffer sieve-manage-server sieve-manage-port)
257             ((error quit) nil)))
258     (when sieve-manage-process
259       (while (and (eq sieve-manage-state 'initial)
260                   (memq (process-status sieve-manage-process) '(open run)))
261         (message "Waiting for response from %s..." sieve-manage-server)
262         (accept-process-output sieve-manage-process 1))
263       (message "Waiting for response from %s...done" sieve-manage-server)
264       (and (memq (process-status sieve-manage-process) '(open run))
265            sieve-manage-process))))
266
267 ;; Streams
268
269 (defun sieve-manage-network-p (buffer)
270   t)
271
272 (defun sieve-manage-network-open (name buffer server port)
273   (let* ((port (or port sieve-manage-default-port))
274          (coding-system-for-read sieve-manage-coding-system-for-read)
275          (coding-system-for-write sieve-manage-coding-system-for-write)
276          (process (open-network-stream name buffer server port)))
277     (when process
278       (while (and (memq (process-status process) '(open run))
279                   (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
280                   (goto-char (point-min))
281                   (not (sieve-manage-parse-greeting-1)))
282         (accept-process-output process 1)
283         (sit-for 1))
284       (sieve-manage-erase nil buffer)
285       (when (memq (process-status process) '(open run))
286         process))))
287
288 (defun imap-starttls-p (buffer)
289   ;;  (and (imap-capability 'STARTTLS buffer)
290   (condition-case ()
291       (progn
292         (require 'starttls)
293         (call-process "starttls"))
294     (error nil)))
295
296 (defun imap-starttls-open (name buffer server port)
297   (let* ((port (or port sieve-manage-default-port))
298          (coding-system-for-read sieve-manage-coding-system-for-read)
299          (coding-system-for-write sieve-manage-coding-system-for-write)
300          (process (starttls-open-stream name buffer server port))
301          done)
302     (when process
303       (while (and (memq (process-status process) '(open run))
304                   (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
305                   (goto-char (point-min))
306                   (not (sieve-manage-parse-greeting-1)))
307         (accept-process-output process 1)
308         (sit-for 1))
309       (sieve-manage-erase nil buffer)
310       (sieve-manage-send "STARTTLS")
311       (starttls-negotiate process))
312     (when (memq (process-status process) '(open run))
313       process)))
314
315 ;; Authenticators
316
317 (defun sieve-sasl-auth (buffer mech)
318   "Login to server using the SASL MECH method."
319   (message "sieve: Authenticating using %s..." mech)
320   (if (sieve-manage-interactive-login 
321        buffer
322        (lambda (user passwd)
323          (let (client step tag data rsp)
324            (setq client (sasl-make-client (sasl-find-mechanism (list mech))
325                                           user "sieve" sieve-manage-server))
326            (setq sasl-read-passphrase (function (lambda (prompt) passwd)))
327            (setq step (sasl-next-step client nil))
328            (setq tag
329                  (sieve-manage-send
330                   (concat
331                    "AUTHENTICATE \""
332                    mech
333                    "\""
334                    (and (sasl-step-data step)
335                         (concat
336                          " \""
337                          (base64-encode-string
338                           (sasl-step-data step)
339                           'no-line-break)
340                          "\"")))))
341            (catch 'done
342              (while t
343                (setq rsp nil)
344                (goto-char (point-min))
345                (while (null (or (progn
346                                   (setq rsp (sieve-manage-is-string))
347                                   (if (not (and rsp (looking-at
348                                                      sieve-manage-server-eol)))
349                                       (setq rsp nil)
350                                     (goto-char (match-end 0))
351                                     rsp))
352                                 (setq rsp (sieve-manage-is-okno))))
353                  (accept-process-output sieve-manage-process 1)
354                  (goto-char (point-min)))
355                (sieve-manage-erase)
356                (when (sieve-manage-ok-p rsp)
357                  (when (string-match "^SASL \"\\([^\"]+\\)\"" (cadr rsp))
358                    (sasl-step-set-data
359                     step (base64-decode-string (match-string 1 (cadr rsp)))))
360                  (if (and (setq step (sasl-next-step client step))
361                           (setq data (sasl-step-data step)))
362                      ;; We got data for server but it's finished
363                      (error "Server not ready for SASL data: %s" data)
364                    ;; The authentication process is finished.
365                    (throw 'done t)))
366                (unless (stringp rsp)
367                  (apply 'error "Server aborted SASL authentication: %s %s %s"
368                         rsp))
369                (sasl-step-set-data step (base64-decode-string rsp))
370                (setq step (sasl-next-step client step))
371                (sieve-manage-send
372                 (if (sasl-step-data step)
373                     (concat "\""
374                             (base64-encode-string (sasl-step-data step)
375                                                   'no-line-break)
376                             "\"")
377                   "")))))))
378       (message "sieve: Authenticating using %s...done" mech)
379     (message "sieve: Authenticating using %s...failed" mech)))
380
381 (defun sieve-manage-cram-md5-p (buffer)
382   (sieve-manage-capability "SASL" "CRAM-MD5" buffer))
383
384 (defun sieve-manage-cram-md5-auth (buffer)
385   "Login to managesieve server using the CRAM-MD5 SASL method."
386   (sieve-sasl-auth buffer "CRAM-MD5"))
387
388 (defun sieve-manage-digest-md5-p (buffer)
389   (sieve-manage-capability "SASL" "DIGEST-MD5" buffer))
390
391 (defun sieve-manage-digest-md5-auth (buffer)
392   "Login to managesieve server using the DIGEST-MD5 SASL method."
393   (sieve-sasl-auth buffer "DIGEST-MD5"))
394
395 (defun sieve-manage-scram-md5-p (buffer)
396   (sieve-manage-capability "SASL" "SCRAM-MD5" buffer))
397
398 (defun sieve-manage-scram-md5-auth (buffer)
399   "Login to managesieve server using the SCRAM-MD5 SASL method."
400   (sieve-sasl-auth buffer "SCRAM-MD5"))
401
402 (defun sieve-manage-ntlm-p (buffer)
403   (sieve-manage-capability "SASL" "NTLM" buffer))
404
405 (defun sieve-manage-ntlm-auth (buffer)
406   "Login to managesieve server using the NTLM SASL method."
407   (sieve-sasl-auth buffer "NTLM"))
408
409 (defun sieve-manage-plain-p (buffer)
410   (sieve-manage-capability "SASL" "PLAIN" buffer))
411
412 (defun sieve-manage-plain-auth (buffer)
413   "Login to managesieve server using the PLAIN SASL method."
414   (sieve-sasl-auth buffer "PLAIN"))
415
416 (defun sieve-manage-login-p (buffer)
417   (sieve-manage-capability "SASL" "LOGIN" buffer))
418
419 (defun sieve-manage-login-auth (buffer)
420   "Login to managesieve server using the LOGIN SASL method."
421   (sieve-sasl-auth buffer "LOGIN"))
422
423 ;; Managesieve API
424
425 (defun sieve-manage-open (server &optional port stream auth buffer)
426   "Open a network connection to a managesieve SERVER (string).
427 Optional variable PORT is port number (integer) on remote server.
428 Optional variable STREAM is any of `sieve-manage-streams' (a symbol).
429 Optional variable AUTH indicates authenticator to use, see
430 `sieve-manage-authenticators' for available authenticators.  If nil, chooses
431 the best stream the server is capable of.
432 Optional variable BUFFER is buffer (buffer, or string naming buffer)
433 to work in."
434   (setq buffer (or buffer (format " *sieve* %s:%d" server (or port 2000))))
435   (with-current-buffer (get-buffer-create buffer)
436     (mapc 'make-variable-buffer-local sieve-manage-local-variables)
437     (sieve-manage-disable-multibyte)
438     (buffer-disable-undo)
439     (setq sieve-manage-server (or server sieve-manage-server))
440     (setq sieve-manage-port (or port sieve-manage-port))
441     (setq sieve-manage-stream (or stream sieve-manage-stream))
442     (message "sieve: Connecting to %s..." sieve-manage-server)
443     (if (let ((sieve-manage-stream
444                (or sieve-manage-stream sieve-manage-default-stream)))
445           (sieve-manage-open-1 buffer))
446         ;; Choose stream.
447         (let (stream-changed)
448           (message "sieve: Connecting to %s...done" sieve-manage-server)
449           (when (null sieve-manage-stream)
450             (let ((streams sieve-manage-streams))
451               (while (setq stream (pop streams))
452                 (if (funcall (nth 1 (assq stream
453                                           sieve-manage-stream-alist)) buffer)
454                     (setq stream-changed
455                           (not (eq (or sieve-manage-stream
456                                        sieve-manage-default-stream)
457                                    stream))
458                           sieve-manage-stream stream
459                           streams nil)))
460               (unless sieve-manage-stream
461                 (error "Couldn't figure out a stream for server"))))
462           (when stream-changed
463             (message "sieve: Reconnecting with stream `%s'..."
464                      sieve-manage-stream)
465             (sieve-manage-close buffer)
466             (if (sieve-manage-open-1 buffer)
467                 (message "sieve: Reconnecting with stream `%s'...done"
468                          sieve-manage-stream)
469               (message "sieve: Reconnecting with stream `%s'...failed"
470                        sieve-manage-stream))
471             (setq sieve-manage-capability nil))
472           (if (sieve-manage-opened buffer)
473               ;; Choose authenticator
474               (when (and (null sieve-manage-auth)
475                          (not (eq sieve-manage-state 'auth)))
476                 (let ((auths sieve-manage-authenticators))
477                   (while (setq auth (pop auths))
478                     (if (funcall (nth 1 (assq
479                                          auth
480                                          sieve-manage-authenticator-alist))
481                                  buffer)
482                         (setq sieve-manage-auth auth
483                               auths nil)))
484                   (unless sieve-manage-auth
485                     (error "Couldn't figure out authenticator for server"))))))
486       (message "sieve: Connecting to %s...failed" sieve-manage-server))
487     (when (sieve-manage-opened buffer)
488       (sieve-manage-erase)
489       buffer)))
490
491 (defun sieve-manage-opened (&optional buffer)
492   "Return non-nil if connection to managesieve server in BUFFER is open.
493 If BUFFER is nil then the current buffer is used."
494   (and (setq buffer (get-buffer (or buffer (current-buffer))))
495        (buffer-live-p buffer)
496        (with-current-buffer buffer
497          (and sieve-manage-process
498               (memq (process-status sieve-manage-process) '(open run))))))
499
500 (defun sieve-manage-close (&optional buffer)
501   "Close connection to managesieve server in BUFFER.
502 If BUFFER is nil, the current buffer is used."
503   (with-current-buffer (or buffer (current-buffer))
504     (when (sieve-manage-opened)
505       (sieve-manage-send "LOGOUT")
506       (sit-for 1))
507     (when (and sieve-manage-process
508                (memq (process-status sieve-manage-process) '(open run)))
509       (delete-process sieve-manage-process))
510     (setq sieve-manage-process nil)
511     (sieve-manage-erase)
512     t))
513
514 (defun sieve-manage-authenticate (&optional user passwd buffer)
515   "Authenticate to server in BUFFER, using current buffer if nil.
516 It uses the authenticator specified when opening the server.  If the
517 authenticator requires username/passwords, they are queried from the
518 user and optionally stored in the buffer.  If USER and/or PASSWD is
519 specified, the user will not be questioned and the username and/or
520 password is remembered in the buffer."
521   (with-current-buffer (or buffer (current-buffer))
522     (if (not (eq sieve-manage-state 'nonauth))
523         (eq sieve-manage-state 'auth)
524       (make-variable-buffer-local 'sieve-manage-username)
525       (make-variable-buffer-local 'sieve-manage-password)
526       (if user (setq sieve-manage-username user))
527       (if passwd (setq sieve-manage-password passwd))
528       (if (funcall (nth 2 (assq sieve-manage-auth
529                                 sieve-manage-authenticator-alist)) buffer)
530           (setq sieve-manage-state 'auth)))))
531
532 (defun sieve-manage-capability (&optional name value buffer)
533   (with-current-buffer (or buffer (current-buffer))
534     (if (null name)
535         sieve-manage-capability
536       (if (null value)
537           (nth 1 (assoc name sieve-manage-capability))
538         (when (string-match value (nth 1 (assoc name sieve-manage-capability)))
539           (nth 1 (assoc name sieve-manage-capability)))))))
540
541 (defun sieve-manage-listscripts (&optional buffer)
542   (with-current-buffer (or buffer (current-buffer))
543     (sieve-manage-send "LISTSCRIPTS")
544     (sieve-manage-parse-listscripts)))
545
546 (defun sieve-manage-havespace (name size &optional buffer)
547   (with-current-buffer (or buffer (current-buffer))
548     (sieve-manage-send (format "HAVESPACE \"%s\" %s" name size))
549     (sieve-manage-parse-okno)))
550
551 (eval-and-compile
552   (if (fboundp 'string-bytes)
553       (defalias 'sieve-string-bytes 'string-bytes)
554     (defalias 'sieve-string-bytes 'length)))
555
556 (defun sieve-manage-putscript (name content &optional buffer)
557   (with-current-buffer (or buffer (current-buffer))
558     (sieve-manage-send (format "PUTSCRIPT \"%s\" {%d+}%s%s" name
559                                (sieve-string-bytes content)
560                                sieve-manage-client-eol content))
561     (sieve-manage-parse-okno)))
562
563 (defun sieve-manage-deletescript (name &optional buffer)
564   (with-current-buffer (or buffer (current-buffer))
565     (sieve-manage-send (format "DELETESCRIPT \"%s\"" name))
566     (sieve-manage-parse-okno)))
567
568 (defun sieve-manage-getscript (name output-buffer &optional buffer)
569   (with-current-buffer (or buffer (current-buffer))
570     (sieve-manage-send (format "GETSCRIPT \"%s\"" name))
571     (let ((script (sieve-manage-parse-string)))
572       (sieve-manage-parse-crlf)
573       (with-current-buffer output-buffer
574         (insert script))
575       (sieve-manage-parse-okno))))
576
577 (defun sieve-manage-setactive (name &optional buffer)
578   (with-current-buffer (or buffer (current-buffer))
579     (sieve-manage-send (format "SETACTIVE \"%s\"" name))
580     (sieve-manage-parse-okno)))
581
582 ;; Protocol parsing routines
583
584 (defun sieve-manage-ok-p (rsp)
585   (string= (downcase (or (car-safe rsp) "")) "ok"))
586
587 (defsubst sieve-manage-forward ()
588   (or (eobp) (forward-char)))
589
590 (defun sieve-manage-is-okno ()
591   (when (looking-at (concat
592                      "^\\(OK\\|NO\\)\\( (\\([^)]+\\))\\)?\\( \\(.*\\)\\)?"
593                      sieve-manage-server-eol))
594     (let ((status (match-string 1))
595           (resp-code (match-string 3))
596           (response (match-string 5)))
597       (when response
598         (goto-char (match-beginning 5))
599         (setq response (sieve-manage-is-string)))
600       (list status resp-code response))))
601
602 (defun sieve-manage-parse-okno ()
603   (let (rsp)
604     (while (null rsp)
605       (accept-process-output (get-buffer-process (current-buffer)) 1)
606       (goto-char (point-min))
607       (setq rsp (sieve-manage-is-okno)))
608     (sieve-manage-erase)
609     rsp))
610
611 (defun sieve-manage-parse-capability-1 ()
612   "Accept a managesieve greeting."
613   (let (str)
614     (while (setq str (sieve-manage-is-string))
615       (if (eq (char-after) ? )
616           (progn
617             (sieve-manage-forward)
618             (push (list str (sieve-manage-is-string))
619                   sieve-manage-capability))
620         (push (list str) sieve-manage-capability))
621       (forward-line)))
622   (when (re-search-forward (concat "^OK" sieve-manage-server-eol) nil t)
623     (setq sieve-manage-state 'nonauth)))
624
625 (defalias 'sieve-manage-parse-greeting-1 'sieve-manage-parse-capability-1)
626
627 (defun sieve-manage-is-string ()
628   (cond ((looking-at "\"\\([^\"]+\\)\"")
629          (prog1
630              (match-string 1)
631            (goto-char (match-end 0))))
632         ((looking-at (concat "{\\([0-9]+\\)}" sieve-manage-server-eol))
633          (let ((pos (match-end 0))
634                (len (string-to-number (match-string 1))))
635            (if (< (point-max) (+ pos len))
636                nil
637              (goto-char (+ pos len))
638              (buffer-substring pos (+ pos len)))))))
639
640 (defun sieve-manage-parse-string ()
641   (let (rsp)
642     (while (null rsp)
643       (accept-process-output (get-buffer-process (current-buffer)) 1)
644       (goto-char (point-min))
645       (setq rsp (sieve-manage-is-string)))
646     (sieve-manage-erase (point))
647     rsp))
648
649 (defun sieve-manage-parse-crlf ()
650   (when (looking-at sieve-manage-server-eol)
651     (sieve-manage-erase (match-end 0))))
652
653 (defun sieve-manage-parse-listscripts ()
654   (let (tmp rsp data)
655     (while (null rsp)
656       (while (null (or (setq rsp (sieve-manage-is-okno))
657                        (setq tmp (sieve-manage-is-string))))
658         (accept-process-output (get-buffer-process (current-buffer)) 1)
659         (goto-char (point-min)))
660       (when tmp
661         (while (not (looking-at (concat "\\( ACTIVE\\)?"
662                                         sieve-manage-server-eol)))
663           (accept-process-output (get-buffer-process (current-buffer)) 1)
664           (goto-char (point-min)))
665         (if (match-string 1)
666             (push (cons 'active tmp) data)
667           (push tmp data))
668         (goto-char (match-end 0))
669         (setq tmp nil)))
670     (sieve-manage-erase)
671     (if (sieve-manage-ok-p rsp)
672         data
673       rsp)))
674
675 (defun sieve-manage-send (cmdstr)
676   (setq cmdstr (concat cmdstr sieve-manage-client-eol))
677   (and sieve-manage-log
678        (with-current-buffer (get-buffer-create sieve-manage-log)
679          (sieve-manage-disable-multibyte)
680          (buffer-disable-undo)
681          (goto-char (point-max))
682          (insert cmdstr)))
683   (process-send-string sieve-manage-process cmdstr))
684
685 (provide 'sieve-manage)
686
687 ;; sieve-manage.el ends here