(pop3-movemail): Fix typo.
[elisp/gnus.git-] / lisp / pop3.el
1 ;;; pop3.el --- Post Office Protocol (RFC 1460) interface
2
3 ;; Copyright (C) 1996-1999 Free Software Foundation, Inc.
4
5 ;; Author: Richard L. Pieri <ratinox@peorth.gweep.net>
6 ;;      Daiki Ueno  <ueno@ueda.info.waseda.ac.jp>
7 ;; Keywords: mail, pop3
8 ;; Version: 1.3s
9
10 ;; This file is part of GNU Emacs.
11
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
16
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
26
27 ;;; Commentary:
28
29 ;; Most of the standard Post Office Protocol version 3 (RFC 1460) commands
30 ;; are implemented.  The LIST command has not been implemented due to lack
31 ;; of actual usefulness.
32 ;; The optional POP3 command TOP has not been implemented.
33
34 ;; This program was inspired by Kyle E. Jones's vm-pop program.
35
36 ;;; Code:
37
38 (require 'mail-utils)
39 (provide 'pop3)
40
41 (defconst pop3-version "1.3s")
42
43 (defvar pop3-maildrop (or (user-login-name) (getenv "LOGNAME") (getenv "USER") nil)
44   "*POP3 maildrop.")
45 (defvar pop3-mailhost (or (getenv "MAILHOST") nil)
46   "*POP3 mailhost.")
47 (defvar pop3-port 110
48   "*POP3 port.")
49 (defvar pop3-connection-type nil
50   "*POP3 connection type.")
51
52 (defvar pop3-password-required t
53   "*Non-nil if a password is required when connecting to POP server.")
54 (defvar pop3-password nil
55   "*Password to use when connecting to POP server.")
56
57 (defvar pop3-authentication-scheme 'pass
58   "*POP3 authentication scheme.
59 Defaults to 'pass, for the standard USER/PASS authentication.  Other valid
60 values are 'apop.")
61
62 (defvar pop3-timestamp nil
63   "Timestamp returned when initially connected to the POP server.
64 Used for APOP authentication.")
65
66 (defvar pop3-leave-mail-on-server nil
67   "Non-nil if mail is to be left on the server and UIDL used for 
68 message retrieval.")
69
70 (defvar pop3-maximum-message-size nil
71   "If non-nil only download messages smaller than this.")
72
73 (defvar pop3-except-header-regexp nil
74   "If non-nil we do not retrieve messages whose headers are matching this regexp.")
75
76 (defvar pop3-uidl-file-name "~/.uidls"
77   "File in which to store the UIDL of processed messages.")
78
79 (defvar pop3-uidl-support 'dont-know
80   "Whether the server supports UIDL.
81 Nil means no, t means yes, not-nil-or-t means yet to be determined.")
82
83 (defvar pop3-uidl-obarray (make-vector 31 0)
84   "Uidl hash table.")
85
86 (defvar pop3-read-point nil)
87 (defvar pop3-debug nil)
88
89 (eval-and-compile
90   (autoload 'open-ssl-stream "ssl"))
91
92 (defvar pop3-ssl-program-arguments
93   '("-quiet")
94   "Arguments to be passed to the program `pop3-ssl-program-name'.")
95
96 (defun pop3-movemail (&optional crashbox)
97   "Transfer contents of a maildrop to the specified CRASHBOX."
98   (or crashbox (setq crashbox (expand-file-name "~/.crashbox")))
99   (let* ((process (pop3-open-server pop3-mailhost pop3-port))
100          (crashbuf (get-buffer-create " *pop3-retr*"))
101          (n 1)
102          (pop3-password pop3-password)
103          (pop3-uidl-file-name 
104           (convert-standard-filename 
105            (concat pop3-uidl-file-name "-" pop3-mailhost)))
106          (retrieved-messages nil)
107          messages message-count)
108     ;; for debugging only
109     (if pop3-debug (switch-to-buffer (process-buffer process)))
110     ;; query for password
111     (if (and pop3-password-required (not pop3-password))
112         (setq pop3-password
113               (pop3-read-passwd (format "Password for %s: " pop3-maildrop))))
114     (cond ((equal 'apop pop3-authentication-scheme)
115            (pop3-apop process pop3-maildrop))
116           ((equal 'pass pop3-authentication-scheme)
117            (pop3-user process pop3-maildrop)
118            (pop3-pass process))
119           (t (error "Invalid POP3 authentication scheme.")))
120     ;; get messages that are suitable for download
121     (message "Retrieving message list...")
122     (setq messages (pop3-get-message-numbers process)
123           message-count (length (cdr messages)))
124     (message (format "Retrieving message list...%d of %d unread" 
125                      message-count (pop messages)))
126     (unwind-protect
127         (unless (not (stringp crashbox))
128           (while messages
129             (message 
130              (format "Retrieving message %d of %d (%d octets) from %s..."
131                      n message-count (cdar messages) pop3-mailhost))
132             (pop3-retr process (caar messages) crashbuf)
133             (push (caar messages) retrieved-messages)
134             (setq messages (cdr messages)
135                   n (1+ n)))
136           (with-current-buffer crashbuf
137             (write-region-as-binary (point-min) (point-max)
138                                     crashbox 'append 'nomesg))
139           ;; mark messages as read
140           (when pop3-leave-mail-on-server
141             (pop3-save-uidls))
142           ;; now delete the messages we have retrieved
143           (unless pop3-leave-mail-on-server
144             (dolist (n retrieved-messages)
145               (message (format "Deleting message %d of %d from %s..."
146                                n message-count pop3-mailhost))
147               (pop3-dele process n)))
148           )
149       (pop3-quit process))
150     (kill-buffer crashbuf)
151     message-count))
152
153 (defun pop3-open-server (mailhost port)
154   "Open TCP connection to MAILHOST.
155 Returns the process associated with the connection."
156   (let ((process-buffer
157          (get-buffer-create (format "trace of POP session to %s" mailhost)))
158         (process))
159     (save-excursion
160       (set-buffer process-buffer)
161       (erase-buffer))
162     (setq
163      process
164      (cond
165       ((eq pop3-connection-type 'ssl)
166        (pop3-open-ssl-stream "POP" process-buffer mailhost port))
167       (t
168        (open-network-stream-as-binary "POP" process-buffer mailhost port))))
169     (setq pop3-read-point (point-min))
170     (let ((response (pop3-read-response process t)))
171       (setq pop3-timestamp
172             (substring response (or (string-match "<" response) 0)
173                        (+ 1 (or (string-match ">" response) -1)))))
174     process))
175
176 (defun pop3-open-ssl-stream-1 (name buffer host service extra-arg)
177   (let* ((ssl-program-arguments 
178           `(,@pop3-ssl-program-arguments ,extra-arg
179             "-connect" ,(format "%s:%d" host service)))
180          (process (open-ssl-stream name buffer host service)))
181     (when process
182       (with-current-buffer buffer
183         (goto-char (point-min))
184         (while (and (memq (process-status process) '(open run))
185                     (goto-char (point-max))
186                     (forward-line -1)
187                     (not (looking-at "+OK")))
188           (accept-process-output process 1)
189           (sit-for 1))
190         (delete-region (point-min) (point)))
191       (and process (memq (process-status process) '(open run))
192            process))))
193
194 (defun pop3-open-ssl-stream (name buffer host service)
195   "Open a SSL connection for a service to a host."
196   (as-binary-process
197    (or (pop3-open-ssl-stream-1 name buffer host service "-ssl3")
198        (pop3-open-ssl-stream-1 name buffer host service "-ssl2"))))
199
200 ;; Support functions
201
202 (defun pop3-process-filter (process output)
203   (save-excursion
204     (set-buffer (process-buffer process))
205     (goto-char (point-max))
206     (insert output)))
207
208 (defun pop3-send-command (process command)
209     (set-buffer (process-buffer process))
210     (goto-char (point-max))
211 ;;    (if (= (aref command 0) ?P)
212 ;;      (insert "PASS <omitted>\r\n")
213 ;;      (insert command "\r\n"))
214     (setq pop3-read-point (point))
215     (goto-char (point-max))
216     (process-send-string process (concat command "\r\n"))
217     )
218
219 (defun pop3-read-response (process &optional return)
220   "Read the response from the server.
221 Return the response string if optional second argument is non-nil."
222   (let ((case-fold-search nil)
223         match-end)
224     (save-excursion
225       (set-buffer (process-buffer process))
226       (goto-char pop3-read-point)
227       (while (not (search-forward "\r\n" nil t))
228         (accept-process-output process 3)
229         (goto-char pop3-read-point))
230       (setq match-end (point))
231       (goto-char pop3-read-point)
232       (if (looking-at "-ERR")
233           (signal 'error (list (buffer-substring (point) (- match-end 2))))
234         (if (not (looking-at "+OK"))
235             (progn (setq pop3-read-point match-end) nil)
236           (setq pop3-read-point match-end)
237           (if return
238               (buffer-substring (point) match-end)
239             t)
240           )))))
241
242 (defvar pop3-read-passwd nil)
243 (defun pop3-read-passwd (prompt)
244   (if (not pop3-read-passwd)
245       (if (functionp 'read-passwd)
246           (setq pop3-read-passwd 'read-passwd)
247         (if (load "passwd" t)
248             (setq pop3-read-passwd 'read-passwd)
249           (autoload 'ange-ftp-read-passwd "ange-ftp")
250           (setq pop3-read-passwd 'ange-ftp-read-passwd))))
251   (funcall pop3-read-passwd prompt))
252
253 (defun pop3-clean-region (start end)
254   (setq end (set-marker (make-marker) end))
255   (save-excursion
256     (goto-char start)
257     (while (and (< (point) end) (search-forward "\r\n" end t))
258       (replace-match "\n" t t))
259     (goto-char start)
260     (while (re-search-forward "\n\n\\(From \\)" end t)
261       (replace-match "\n\n>\\1" t nil))
262     (goto-char start)
263     (while (and (< (point) end) (re-search-forward "^\\." end t))
264       (replace-match "" t t)
265       (forward-char)))
266   (set-marker end nil))
267
268 (defun pop3-munge-message-separator (start end)
269   "Check to see if a message separator exists.  If not, generate one."
270   (if (not (fboundp 'parse-time-string))
271       (autoload 'parse-time-string "parse-time"))
272   (save-excursion
273     (save-restriction
274       (narrow-to-region start end)
275       (goto-char (point-min))
276       (if (not (or (looking-at "From .?") ; Unix mail
277                    (looking-at "\001\001\001\001\n") ; MMDF
278                    (looking-at "BABYL OPTIONS:") ; Babyl
279                    ))
280           (let ((from (mail-strip-quoted-names (mail-fetch-field "From")))
281                 (date (mail-fetch-field "Date"))
282                 (From_))
283             ;; sample date formats I have seen
284             ;; Date: Tue, 9 Jul 1996 09:04:21 -0400 (EDT)
285             ;; Date: 08 Jul 1996 23:22:24 -0400
286             ;; should be
287             ;; Tue Jul 9 09:04:21 1996
288             (setq date (format-time-string
289                         "%a %b %e %T %Y"
290                         (if date
291                             (condition-case nil
292                                 (apply 'encode-time (parse-time-string date))
293                               (error (current-time)))
294                           (current-time))))
295             (setq From_ (format "\nFrom %s  %s\n" from date))
296             (while (string-match "," From_)
297               (setq From_ (concat (substring From_ 0 (match-beginning 0))
298                                   (substring From_ (match-end 0)))))
299             (goto-char (point-min))
300             (insert From_))))))
301
302 ;; UIDL support
303
304 (defun pop3-get-message-numbers (process)
305   "Get the list of message numbers and lengths to retrieve via PROCESS."
306   ;; we use the LIST comand first anyway to get the message lengths.
307   ;; then if we're leaving mail on the server, see if the UIDL command
308   ;; is implemented. if so, we use it to get the message number list.
309   (let* ((messages (pop3-list process))
310          (total (or (pop messages) 0))
311          (uidl (if pop3-leave-mail-on-server
312                    (pop3-get-uidl process)))
313          out)
314     (while messages
315       ;; only retrieve messages matching our regexp or in the uidl list
316       (when (and
317              ;; remove elements not in the uidl, this assumes the uidl is short
318              (or (not (eq pop3-uidl-support t))
319                  (memq (caar messages) uidl))
320              (caar messages)
321              ;; don't download messages that are too large
322              (not (and pop3-maximum-message-size
323                        (> (cdar messages) pop3-maximum-message-size)))
324              (not (and pop3-except-header-regexp
325                        (string-match pop3-except-header-regexp
326                                      (pop3-top process (caar messages) 0)))))
327         (push (car messages) out))
328       (setq messages (cdr messages)))
329     (cons total (reverse out))))
330
331 (defun pop3-get-uidl (process)
332   "Use PROCESS to get a list of unread message numbers."
333   (let ((messages (pop3-uidl process)) uidl)
334     (if (or (null messages) (null pop3-uidl-support))
335         (setq pop3-uidl-support nil)
336       (setq pop3-uidl-support t)
337       (save-excursion
338         (with-temp-buffer
339           (when (file-readable-p pop3-uidl-file-name)
340             (insert-file-contents pop3-uidl-file-name))
341           (goto-char (point-min))
342           (while (looking-at "\\([^ \n\t]+\\)")
343             (set (intern (match-string 1) pop3-uidl-obarray)
344                  (cons nil t))
345             (forward-line 1))
346           ))
347       (dolist (message (cdr messages))
348         (if (setq uidl (intern-soft (cdr message) pop3-uidl-obarray))
349             (setcar (symbol-value uidl) (car message))
350           (set (intern (cdr message) pop3-uidl-obarray)
351                (cons (car message) nil))))
352       (pop3-get-unread-message-numbers))
353     ))
354
355 (defun pop3-get-unread-message-numbers ()
356   "Return a sorted list of unread msg numbers to retrieve."
357   (let (nums)
358     (mapatoms (lambda (atom)
359                 (if (not (cdr (symbol-value atom)))
360                     (push (car (symbol-value atom)) nums)))
361               pop3-uidl-obarray)
362     (sort nums '<)))
363
364 (defun pop3-save-uidls ()
365   "Save the updated UIDLs to disk for use next time."
366   (when (and pop3-leave-mail-on-server 
367              pop3-uidl-obarray
368              (catch 'found
369                (dotimes (i (length pop3-uidl-obarray))
370                  (if (symbolp (aref pop3-uidl-obarray i))
371                      (throw 'found t)))))
372     (when (file-readable-p pop3-uidl-file-name)
373       (copy-file pop3-uidl-file-name
374                  (concat pop3-uidl-file-name ".old")
375                  'overwrite 'keeptime))
376     (save-excursion
377       (with-temp-file pop3-uidl-file-name
378         (mapatoms 
379          (lambda (atom)
380            (when (car (symbol-value atom))
381              (insert (format "%s\n" atom))
382              (unintern atom pop3-uidl-obarray)))
383          pop3-uidl-obarray)))))
384     
385
386 ;; The Command Set
387
388 ;; AUTHORIZATION STATE
389
390 (defun pop3-user (process user)
391   "Send USER information to POP3 server."
392   (pop3-send-command process (format "USER %s" user))
393   (let ((response (pop3-read-response process t)))
394     (if (not (and response (string-match "+OK" response)))
395         (error (format "USER %s not valid." user)))))
396
397 (defun pop3-pass (process)
398   "Send authentication information to the server."
399   (pop3-send-command process (format "PASS %s" pop3-password))
400   (let ((response (pop3-read-response process t)))
401     (if (not (and response (string-match "+OK" response)))
402         (pop3-quit process))))
403
404 (defun pop3-apop (process user)
405   "Send alternate authentication information to the server."
406   (if (not (fboundp 'md5)) (autoload 'md5 "md5"))
407   (let ((hash (md5 (concat pop3-timestamp pop3-password))))
408     (pop3-send-command process (format "APOP %s %s" user hash))
409     (let ((response (pop3-read-response process t)))
410       (if (not (and response (string-match "+OK" response)))
411           (pop3-quit process)))))
412
413 ;; TRANSACTION STATE
414
415 (defun pop3-stat (process)
416   "Return the number of messages in the maildrop and the maildrop's size."
417   (pop3-send-command process "STAT")
418   (let ((response (pop3-read-response process t)))
419     (list (string-to-int (nth 1 (split-string response)))
420           (string-to-int (nth 2 (split-string response))))
421     ))
422
423 (defun pop3-retr (process msg crashbuf)
424   "Retrieve message-id MSG to buffer CRASHBUF."
425   (pop3-send-command process (format "RETR %s" msg))
426   (pop3-read-response process)
427   (save-excursion
428     (let ((region (pop3-get-extended-response process)))
429       (pop3-munge-message-separator (car region) (cadr region))
430       (append-to-buffer crashbuf (car region) (cadr region))
431       (delete-region (car region) (cadr region))
432       )))
433
434 (defun pop3-dele (process msg)
435   "Mark message-id MSG as deleted."
436   (pop3-send-command process (format "DELE %s" msg))
437   (pop3-read-response process))
438
439 (defun pop3-noop (process msg)
440   "No-operation."
441   (pop3-send-command process "NOOP")
442   (pop3-read-response process))
443
444 (defun pop3-last (process)
445   "Return highest accessed message-id number for the session."
446   (pop3-send-command process "LAST")
447   (let ((response (pop3-read-response process t)))
448     (string-to-int (nth 1 (split-string response)))
449     ))
450
451 (defun pop3-rset (process)
452   "Remove all delete marks from current maildrop."
453   (pop3-send-command process "RSET")
454   (pop3-read-response process))
455
456 ;; UPDATE
457
458 (defun pop3-quit (process)
459   "Close connection to POP3 server.
460 Tell server to remove all messages marked as deleted, unlock the maildrop,
461 and close the connection."
462   (pop3-send-command process "QUIT")
463   (pop3-read-response process t)
464   (when process
465     (save-excursion
466       (set-buffer (process-buffer process))
467       (goto-char (point-max))
468       (delete-process process)
469       )))
470
471 (defun pop3-uidl (process &optional msgno)
472   "Return the results of a UIDL command in PROCESS for optional MSGNO.
473 If UIDL is unsupported on this mail server or if msgno is invalid, return nil.
474 Otherwise, return a list in the form
475
476    (N (1 UIDL-1) (2 UIDL-2) ... (N UIDL-N))
477
478 where
479
480    N is an integer for the number of UIDLs returned (could be 0)
481    UIDL-n is a string."
482
483   (if msgno
484       (pop3-send-command process (format "UIDL %d" msgno))
485     (pop3-send-command process "UIDL"))
486   
487   (if (null (pop3-read-response process t))
488       nil ;; UIDL is not supported on this server
489     (let (pairs uidl)
490       (save-excursion
491         (save-restriction
492           (apply 'narrow-to-region (pop3-get-extended-response process))
493           (goto-char (point-min))
494           (while (looking-at "\\([^ \n\t]*\\) \\([^ \n\t]*\\)")
495             (setq msgno (string-to-int (match-string 1))
496                   uidl (match-string 2))
497             (push (cons msgno uidl) pairs)
498             (beginning-of-line 2))
499           (cons (length pairs) (nreverse pairs))
500           )))))
501
502 (defun pop3-list (process &optional msgno)
503   "Return the results of a LIST command for PROCESS and optional MSGNO.
504 If (optional) msgno is invalid, return nil.  Otherwise, return a list
505 in the form
506
507    (N (1 LEN-1) (2 LEN-2) ... (N LEN-N))
508
509 where
510
511    N is an integer for the number of msg/len pairs (could be 0)
512    LEN-n is an integer."
513   (if msgno
514       (pop3-send-command process (format "LIST %d" msgno))
515     (pop3-send-command process "LIST"))
516
517   (if (null (pop3-read-response process t))
518       nil ;; MSGNO is not valid number
519     (let (pairs len)
520       (save-excursion
521         (save-restriction
522           (apply 'narrow-to-region (pop3-get-extended-response process))
523           (goto-char (point-min))
524           (while (looking-at "\\([^ \n\t]*\\) \\([^ \n\t]*\\)")
525             (setq msgno (string-to-int (match-string 1))
526                   len (string-to-int (match-string 2)))
527             (push (cons msgno len) pairs)
528             (beginning-of-line 2))
529           (cons (length pairs) (nreverse pairs))
530           )))))
531
532 (defun pop3-top (process msgno &optional lines)
533   "Return the top LINES of messages for PROCESS and MSGNO.
534 If msgno is invalid, return nil.  Otherwise, return a string."
535   (pop3-send-command process (format "TOP %d %d" msgno (or lines 1)))
536   (if (pop3-read-response process t)
537       nil ;; MSGNO is not valid number
538     (save-excursion
539       (apply 'buffer-substring (pop3-get-extended-response process)))
540     ))
541
542 ;;; Utility code
543
544 (defun pop3-get-extended-response (process)
545   "Get the extended pop3 response in the PROCESS buffer."
546   (let ((start pop3-read-point) end)
547     (set-buffer (process-buffer process))
548     (goto-char start)
549     (while (not (re-search-forward "^\\.\r\n" nil t))
550       (accept-process-output process 3)
551       (goto-char start))
552     (setq pop3-read-point (point-marker))
553     (goto-char (match-beginning 0))
554     (setq end (point-marker))
555     (pop3-clean-region start end)
556     (list start end)))
557
558 \f
559 ;; Summary of POP3 (Post Office Protocol version 3) commands and responses
560
561 ;;; AUTHORIZATION STATE
562
563 ;; Initial TCP connection
564 ;; Arguments: none
565 ;; Restrictions: none
566 ;; Possible responses:
567 ;;  +OK [POP3 server ready]
568
569 ;; USER name
570 ;; Arguments: a server specific user-id (required)
571 ;; Restrictions: authorization state [after unsuccessful USER or PASS
572 ;; Possible responses:
573 ;;  +OK [valid user-id]
574 ;;  -ERR [invalid user-id]
575
576 ;; PASS string
577 ;; Arguments: a server/user-id specific password (required)
578 ;; Restrictions: authorization state, after successful USER
579 ;; Possible responses:
580 ;;  +OK [maildrop locked and ready]
581 ;;  -ERR [invalid password]
582 ;;  -ERR [unable to lock maildrop]
583
584 ;;; TRANSACTION STATE
585
586 ;; STAT
587 ;; Arguments: none
588 ;; Restrictions: transaction state
589 ;; Possible responses:
590 ;;  +OK nn mm [# of messages, size of maildrop]
591
592 ;; LIST [msg]
593 ;; Arguments: a message-id (optional)
594 ;; Restrictions: transaction state; msg must not be deleted
595 ;; Possible responses:
596 ;;  +OK [scan listing follows]
597 ;;  -ERR [no such message]
598
599 ;; TOP msg [lines]
600 ;; Arguments: a message-id (required), number of lines (optional)
601 ;; Restrictions: transaction state; msg must not be deleted
602 ;; Possible responses:
603 ;;  +OK [partial message listing follows]
604 ;;  -ERR [no such message]
605
606 ;; UIDL [msg]
607 ;; Arguments: a message-id (optional)
608 ;; Restrictions: transaction state; msg must not be deleted
609 ;; Possible responses:
610 ;;  +OK [uidl listing follows]
611 ;;  -ERR [no such message]
612
613 ;; RETR msg
614 ;; Arguments: a message-id (required)
615 ;; Restrictions: transaction state; msg must not be deleted
616 ;; Possible responses:
617 ;;  +OK [message contents follow]
618 ;;  -ERR [no such message]
619
620 ;; DELE msg
621 ;; Arguments: a message-id (required)
622 ;; Restrictions: transaction state; msg must not be deleted
623 ;; Possible responses:
624 ;;  +OK [message deleted]
625 ;;  -ERR [no such message]
626
627 ;; NOOP
628 ;; Arguments: none
629 ;; Restrictions: transaction state
630 ;; Possible responses:
631 ;;  +OK
632
633 ;; LAST
634 ;; Arguments: none
635 ;; Restrictions: transaction state
636 ;; Possible responses:
637 ;;  +OK nn [highest numbered message accessed]
638
639 ;; RSET
640 ;; Arguments: none
641 ;; Restrictions: transaction state
642 ;; Possible responses:
643 ;;  +OK [all delete marks removed]
644
645 ;;; UPDATE STATE
646
647 ;; QUIT
648 ;; Arguments: none
649 ;; Restrictions: none
650 ;; Possible responses:
651 ;;  +OK [TCP connection closed]