*** empty log message ***
[gnus] / lisp / nntp.el
index 95499b8..8d00f17 100644 (file)
@@ -1,4 +1,5 @@
-;;; Copyright (C) 1987,88,89,90,92,93,94,95,96 Free Software Foundation, Inc.
+;;; nntp.el --- nntp access for Gnus
+;;; Copyright (C) 1987-90,92-97 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
 ;; Keywords: news
   "Port number on the physical nntp server.")
 
 (defvoo nntp-server-opened-hook '(nntp-send-mode-reader)
-  "*Hook used for sending commands to the server at startup.  
+  "*Hook used for sending commands to the server at startup.
 The default value is `nntp-send-mode-reader', which makes an innd
 server spawn an nnrpd server.  Another useful function to put in this
 hook might be `nntp-send-authinfo', which will prompt for a password
 to allow posting from the server.  Note that this is only necessary to
-do on servers that use strict access control.")  
+do on servers that use strict access control.")
 
 (defvoo nntp-authinfo-function 'nntp-send-authinfo
   "Function used to send AUTHINFO to the server.")
 
-(defvoo nntp-server-action-alist 
-  '(("nntpd 1\\.5\\.11t" 
+(defvoo nntp-server-action-alist
+  '(("nntpd 1\\.5\\.11t"
      (remove-hook 'nntp-server-opened-hook 'nntp-send-mode-reader))
-    ("NNRP server Netscape" 
+    ("NNRP server Netscape"
      (setq nntp-server-list-active-group nil)))
   "Alist of regexps to match on server types and actions to be taken.
 For instance, if you want Gnus to beep every time you connect
@@ -72,14 +73,19 @@ It will be called with the buffer to output in.
 
 Two pre-made functions are `nntp-open-network-stream', which is the
 default, and simply connects to some port or other on the remote
-system (see nntp-port-number).  The other are `nntp-open-rlogin', which
-does an rlogin on the remote system, and then does a telnet to the
-NNTP server available there (see nntp-rlogin-parameters) and `nntp-open-telnet' which
-telnets to a remote system, logs in and does the same")
+system (see nntp-port-number).  The other are `nntp-open-rlogin',
+which does an rlogin on the remote system, and then does a telnet to
+the NNTP server available there (see nntp-rlogin-parameters) and
+`nntp-open-telnet' which telnets to a remote system, logs in and does
+the same.")
+
+(defvoo nntp-rlogin-program "rsh"
+  "*Program used to log in on remote machines.
+The default is \"rsh\", but \"ssh\" is a popular alternative.")
 
 (defvoo nntp-rlogin-parameters '("telnet" "-8" "${NNTPSERVER:=news}" "nntp")
   "*Parameters to `nntp-open-login'.
-That function may be used as `nntp-open-server-function'.  In that
+That function may be used as `nntp-open-connection-function'.  In that
 case, this list will be used as the parameter list given to rsh.")
 
 (defvoo nntp-rlogin-user-name nil
@@ -87,7 +93,7 @@ case, this list will be used as the parameter list given to rsh.")
 
 (defvoo nntp-telnet-parameters '("exec" "telnet" "-8" "${NNTPSERVER:=news}" "nntp")
   "*Parameters to `nntp-open-telnet'.
-That function may be used as `nntp-open-server-function'.  In that
+That function may be used as `nntp-open-connection-function'.  In that
 case, this list will be executed as a command after logging in
 via telnet.")
 
@@ -97,6 +103,12 @@ via telnet.")
 (defvoo nntp-telnet-passwd nil
   "Password to use to log in via telnet with.")
 
+(defvoo nntp-telnet-command "telnet"
+  "Command used to start telnet.")
+
+(defvoo nntp-telnet-switches '("-8")
+  "Switches given to the telnet command.")
+
 (defvoo nntp-end-of-line "\r\n"
   "String to use on the end of lines when talking to the NNTP server.
 This is \"\\r\\n\" by default, but should be \"\\n\" when
@@ -121,7 +133,7 @@ The strings are tried in turn until a positive response is gotten.  If
 none of the commands are successful, nntp will just grab headers one
 by one.")
 
-(defvoo nntp-nov-gap 20
+(defvoo nntp-nov-gap 5
   "*Maximum allowed gap between two articles.
 If the gap between two consecutive articles is bigger than this
 variable, split the XOVER request into two requests.")
@@ -141,16 +153,25 @@ server there that you can connect to.  See also `nntp-open-connection-function'"
 (defvoo nntp-warn-about-losing-connection t
   "*If non-nil, beep when a server closes connection.")
 
+(defvoo nntp-coding-system-for-read 'binary
+  "*Coding system to read from NNTP.")
+
+(defvoo nntp-coding-system-for-write 'binary
+    "*Coding system to write to NNTP.")
+
 \f
 
 ;;; Internal variables.
 
+(defvar nntp-have-messaged nil)
+
 (defvar nntp-process-wait-for nil)
 (defvar nntp-process-to-buffer nil)
 (defvar nntp-process-callback nil)
 (defvar nntp-process-decode nil)
 (defvar nntp-process-start-point nil)
 (defvar nntp-inside-change-function nil)
+(defvoo nntp-last-command-time nil)
 
 (defvar nntp-connection-list nil)
 
@@ -159,6 +180,7 @@ server there that you can connect to.  See also `nntp-open-connection-function'"
 (defvoo nntp-status-string "")
 (defconst nntp-version "nntp 5.0")
 (defvoo nntp-inhibit-erase nil)
+(defvoo nntp-inhibit-output nil)
 
 (defvoo nntp-server-xover 'try)
 (defvoo nntp-server-list-active-group 'try)
@@ -168,80 +190,237 @@ server there that you can connect to.  See also `nntp-open-connection-function'"
 
 \f
 
+;;; Internal functions.
+
+(defsubst nntp-send-string (process string)
+  "Send STRING to PROCESS."
+  (process-send-string process (concat string nntp-end-of-line)))
+
+(defsubst nntp-wait-for (process wait-for buffer &optional decode discard)
+  "Wait for WAIT-FOR to arrive from PROCESS."
+  (save-excursion
+    (set-buffer (process-buffer process))
+    (goto-char (point-min))
+    (while (or (not (memq (char-after (point)) '(?2 ?3 ?4 ?5)))
+              (looking-at "480"))
+      (when (looking-at "480")
+       (erase-buffer)
+       (funcall nntp-authinfo-function))
+      (nntp-accept-process-output process)
+      (goto-char (point-min)))
+    (prog1
+       (if (looking-at "[45]")
+           (progn
+             (nntp-snarf-error-message)
+             nil)
+         (goto-char (point-max))
+         (let ((limit (point-min)))
+           (while (not (re-search-backward wait-for limit t))
+             ;; We assume that whatever we wait for is less than 1000
+             ;; characters long.
+             (setq limit (max (- (point-max) 1000) (point-min)))
+             (nntp-accept-process-output process)
+             (goto-char (point-max))))
+         (nntp-decode-text (not decode))
+         (unless discard
+           (save-excursion
+             (set-buffer buffer)
+             (goto-char (point-max))
+             (insert-buffer-substring (process-buffer process))
+             ;; Nix out "nntp reading...." message.
+             (when nntp-have-messaged
+               (setq nntp-have-messaged nil)
+               (message ""))
+             t)))
+      (unless discard
+       (erase-buffer)))))
+
+(defsubst nntp-find-connection (buffer)
+  "Find the connection delivering to BUFFER."
+  (let ((alist nntp-connection-alist)
+       (buffer (if (stringp buffer) (get-buffer buffer) buffer))
+       process entry)
+    (while (setq entry (pop alist))
+      (when (eq buffer (cadr entry))
+       (setq process (car entry)
+             alist nil)))
+    (when process
+      (if (memq (process-status process) '(open run))
+         process
+       (when (buffer-name (process-buffer process))
+         (message "Killed buffer %s" (process-buffer process))
+         (kill-buffer (process-buffer process)))
+       (setq nntp-connection-alist (delq entry nntp-connection-alist))
+       nil))))
+
+(defsubst nntp-find-connection-entry (buffer)
+  "Return the entry for the connection to BUFFER."
+  (assq (nntp-find-connection buffer) nntp-connection-alist))
+
+(defun nntp-find-connection-buffer (buffer)
+  "Return the process connection buffer tied to BUFFER."
+  (let ((process (nntp-find-connection buffer)))
+    (when process
+      (process-buffer process))))
+
+(defsubst nntp-retrieve-data (command address port buffer
+                                  &optional wait-for callback decode)
+  "Use COMMAND to retrieve data into BUFFER from PORT on ADDRESS."
+  (setq nntp-last-command-time (current-time))
+  (let ((process (or (nntp-find-connection buffer)
+                    (nntp-open-connection buffer))))
+    (if (not process)
+       (nnheader-report 'nntp "Couldn't open connection to %s" address)
+      (unless (or nntp-inhibit-erase nnheader-callback-function)
+       (save-excursion
+         (set-buffer (process-buffer process))
+         (erase-buffer)))
+      (when command
+       (nntp-send-string process command))
+      (cond
+       ((eq callback 'ignore)
+       t)
+       ((and callback wait-for)
+       (save-excursion
+         (set-buffer (process-buffer process))
+         (unless nntp-inside-change-function
+           (erase-buffer))
+         (setq nntp-process-decode decode
+               nntp-process-to-buffer buffer
+               nntp-process-wait-for wait-for
+               nntp-process-callback callback
+               nntp-process-start-point (point-max)
+               after-change-functions
+               (list 'nntp-after-change-function-callback)))
+       t)
+       (wait-for
+       (nntp-wait-for process wait-for buffer decode))
+       (t t)))))
+
+(defsubst nntp-send-command (wait-for &rest strings)
+  "Send STRINGS to server and wait until WAIT-FOR returns."
+  (when (and (not nnheader-callback-function)
+            (not nntp-inhibit-output))
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (erase-buffer)))
+  (nntp-retrieve-data
+   (mapconcat 'identity strings " ")
+   nntp-address nntp-port-number nntp-server-buffer
+   wait-for nnheader-callback-function))
+
+(defun nntp-send-command-nodelete (wait-for &rest strings)
+  "Send STRINGS to server and wait until WAIT-FOR returns."
+  (nntp-retrieve-data
+   (mapconcat 'identity strings " ")
+   nntp-address nntp-port-number nntp-server-buffer
+   wait-for nnheader-callback-function))
+
+(defun nntp-send-command-and-decode (wait-for &rest strings)
+  "Send STRINGS to server and wait until WAIT-FOR returns."
+  (when (and (not nnheader-callback-function)
+            (not nntp-inhibit-output))
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (erase-buffer)))
+  (nntp-retrieve-data
+   (mapconcat 'identity strings " ")
+   nntp-address nntp-port-number nntp-server-buffer
+   wait-for nnheader-callback-function t))
+
+(defun nntp-send-buffer (wait-for)
+  "Send the current buffer to server and wait until WAIT-FOR returns."
+  (when (and (not nnheader-callback-function)
+            (not nntp-inhibit-output))
+    (save-excursion
+      (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
+      (erase-buffer)))
+  (nntp-encode-text)
+  (process-send-region (nntp-find-connection nntp-server-buffer)
+                      (point-min) (point-max))
+  (nntp-retrieve-data
+   nil nntp-address nntp-port-number nntp-server-buffer
+   wait-for nnheader-callback-function))
+
+\f
+
 ;;; Interface functions.
 
 (nnoo-define-basics nntp)
 
+(defsubst nntp-next-result-arrived-p ()
+  (let ((point (point)))
+    (cond
+     ((eq (following-char) ?2)
+      (if (re-search-forward "\n\\.\r?\n" nil t)
+         t
+       (goto-char point)
+       nil))
+     ((looking-at "[34]")
+      (forward-line 1)
+      t)
+     (t
+      nil))))
+
 (deffoo nntp-retrieve-headers (articles &optional group server fetch-old)
   "Retrieve the headers of ARTICLES."
   (nntp-possibly-change-group group server)
   (save-excursion
     (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
     (erase-buffer)
-    (if (and (not gnus-nov-is-evil) 
+    (if (and (not gnus-nov-is-evil)
             (not nntp-nov-is-evil)
             (nntp-retrieve-headers-with-xover articles fetch-old))
        ;; We successfully retrieved the headers via XOVER.
         'nov
       ;; XOVER didn't work, so we do it the hard, slow and inefficient
-      ;; way.  
+      ;; way.
       (let ((number (length articles))
            (count 0)
            (received 0)
            (last-point (point-min))
            (buf (nntp-find-connection-buffer nntp-server-buffer))
-           (nntp-inhibit-erase t))
-       ;; Send HEAD command.
-       (while articles
-         (nntp-send-command 
-          nil
-          "HEAD" (if (numberp (car articles)) 
-                     (int-to-string (car articles))
-                   ;; `articles' is either a list of article numbers
-                   ;; or a list of article IDs.
-                   (car articles)))
-         (setq articles (cdr articles)
-               count (1+ count))
-         ;; Every 400 header requests we have to read the stream in
-         ;; order to avoid deadlocks.
-         (when (or (null articles)     ;All requests have been sent.
-                   (zerop (% count nntp-maximum-request)))
-           (nntp-accept-response)
-           (while (progn
-                    (progn
-                      (set-buffer buf)
-                      (goto-char last-point))
-                    ;; Count replies.
-                    (while (re-search-forward "^[0-9]" nil t)
-                      (incf received))
-                    (setq last-point (point))
-                    (< received count))
-             ;; If number of headers is greater than 100, give
-             ;;  informative messages.
-             (and (numberp nntp-large-newsgroup)
-                  (> number nntp-large-newsgroup)
-                  (zerop (% received 20))
-                  (message "NNTP: Receiving headers... %d%%"
-                           (/ (* received 100) number)))
-             (nntp-accept-response))))
-       ;; Wait for text of last command.
-       (goto-char (point-max))
-       (re-search-backward "^[0-9]" nil t)
-       (when (looking-at "^[23]")
+           (nntp-inhibit-erase t)
+           article)
+       ;; Send HEAD commands.
+      (while (setq article (pop articles))
+       (nntp-send-command
+        nil
+        "HEAD" (if (numberp article)
+                   (int-to-string article)
+                 ;; `articles' is either a list of article numbers
+                 ;; or a list of article IDs.
+                 article))
+       (incf count)
+       ;; Every 400 requests we have to read the stream in
+       ;; order to avoid deadlocks.
+       (when (or (null articles)       ;All requests have been sent.
+                 (zerop (% count nntp-maximum-request)))
+         (nntp-accept-response)
          (while (progn
-                  (goto-char (- (point-max) 3))
-                  (not (looking-at "^\\.\r?\n")))
-           (nntp-accept-response)))
+                  (set-buffer buf)
+                  (goto-char last-point)
+                  ;; Count replies.
+                  (while (nntp-next-result-arrived-p)
+                    (setq last-point (point))
+                    (incf received))
+                  (< received count))
+           ;; If number of headers is greater than 100, give
+           ;;  informative messages.
+           (and (numberp nntp-large-newsgroup)
+                (> number nntp-large-newsgroup)
+                (zerop (% received 20))
+                (nnheader-message 6 "NNTP: Receiving headers... %d%%"
+                                  (/ (* received 100) number)))
+           (nntp-accept-response))))
        (and (numberp nntp-large-newsgroup)
             (> number nntp-large-newsgroup)
-            (message "NNTP: Receiving headers...done"))
+            (nnheader-message 6 "NNTP: Receiving headers...done"))
 
        ;; Now all of replies are received.  Fold continuation lines.
        (nnheader-fold-continuation-lines)
        ;; Remove all "\r"'s.
-       (goto-char (point-min))
-       (while (search-forward "\r" nil t)
-         (replace-match "" t t))
+       (nnheader-strip-cr)
        (copy-to-buffer nntp-server-buffer (point-min) (point-max))
        'headers))))
 
@@ -251,7 +430,7 @@ server there that you can connect to.  See also `nntp-open-connection-function'"
   (save-excursion
     (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
     ;; The first time this is run, this variable is `try'.  So we
-    ;; try.   
+    ;; try.
     (when (eq nntp-server-list-active-group 'try)
       (nntp-try-list-active (car groups)))
     (erase-buffer)
@@ -302,17 +481,86 @@ server there that you can connect to.  See also `nntp-open-connection-function'"
        ;; superfluous gunk.
        (goto-char (point-min))
        (while (re-search-forward "^[.2-5]" nil t)
-         (delete-region (match-beginning 0) 
+         (delete-region (match-beginning 0)
                         (progn (forward-line 1) (point))))
        (copy-to-buffer nntp-server-buffer (point-min) (point-max))
        'active))))
 
+(deffoo nntp-retrieve-articles (articles &optional group server)
+  (nntp-possibly-change-group group server)
+  (save-excursion
+    (let ((number (length articles))
+         (count 0)
+         (received 0)
+         (last-point (point-min))
+         (buf (nntp-find-connection-buffer nntp-server-buffer))
+         (nntp-inhibit-erase t)
+         (map (apply 'vector articles))
+         (point 1)
+         article alist)
+      (set-buffer buf)
+      (erase-buffer)
+      ;; Send ARTICLE command.
+      (while (setq article (pop articles))
+       (nntp-send-command
+        nil
+        "ARTICLE" (if (numberp article)
+                      (int-to-string article)
+                    ;; `articles' is either a list of article numbers
+                    ;; or a list of article IDs.
+                    article))
+       (incf count)
+       ;; Every 400 requests we have to read the stream in
+       ;; order to avoid deadlocks.
+       (when (or (null articles)       ;All requests have been sent.
+                 (zerop (% count nntp-maximum-request)))
+         (nntp-accept-response)
+         (while (progn
+                  (set-buffer buf)
+                  (goto-char last-point)
+                  ;; Count replies.
+                  (while (nntp-next-result-arrived-p)
+                    (aset map received (cons (aref map received) (point)))
+                    (setq last-point (point))
+                    (incf received))
+                  (< received count))
+           ;; If number of headers is greater than 100, give
+           ;;  informative messages.
+           (and (numberp nntp-large-newsgroup)
+                (> number nntp-large-newsgroup)
+                (zerop (% received 20))
+                (nnheader-message 6 "NNTP: Receiving articles... %d%%"
+                                  (/ (* received 100) number)))
+           (nntp-accept-response))))
+      (and (numberp nntp-large-newsgroup)
+          (> number nntp-large-newsgroup)
+          (nnheader-message 6 "NNTP: Receiving articles...done"))
+      
+      ;; Now we have all the responses.  We go through the results,
+      ;; washes it and copies it over to the server buffer.
+      (set-buffer nntp-server-buffer)
+      (erase-buffer)
+      (setq last-point (point-min))
+      (mapcar
+       (lambda (entry)
+        (narrow-to-region
+         (setq point (goto-char (point-max)))
+         (progn
+           (insert-buffer-substring buf last-point (cdr entry))
+           (point-max)))
+        (setq last-point (cdr entry))
+        (nntp-decode-text)
+        (widen)
+        (cons (car entry) point))
+       map))))
+
 (defun nntp-try-list-active (group)
   (nntp-list-active-group group)
   (save-excursion
     (set-buffer nntp-server-buffer)
     (goto-char (point-min))
-    (cond ((looking-at "5[0-9]+")
+    (cond ((or (eobp)
+              (looking-at "5[0-9]+"))
           (setq nntp-server-list-active-group nil))
          (t
           (setq nntp-server-list-active-group t)))))
@@ -327,20 +575,22 @@ server there that you can connect to.  See also `nntp-open-connection-function'"
   (when (nntp-send-command-and-decode
         "\r?\n\\.\r?\n" "ARTICLE"
         (if (numberp article) (int-to-string article) article))
-    (when (and buffer
-              (not (equal buffer nntp-server-buffer)))
-      (save-excursion
-       (set-buffer nntp-server-buffer)
-       (copy-to-buffer buffer (point-min) (point-max))
-       (nntp-find-group-and-number)))
-    (nntp-find-group-and-number)))
+    (if (and buffer
+            (not (equal buffer nntp-server-buffer)))
+       (save-excursion
+         (set-buffer nntp-server-buffer)
+         (copy-to-buffer buffer (point-min) (point-max))
+         (nntp-find-group-and-number))
+      (nntp-find-group-and-number))))
 
 (deffoo nntp-request-head (article &optional group server)
   (nntp-possibly-change-group group server)
-  (when (nntp-send-command-and-decode
-        "\r\n\\.\r\n" "HEAD"
+  (when (nntp-send-command
+        "\r?\n\\.\r?\n" "HEAD"
         (if (numberp article) (int-to-string article) article))
-    (nntp-find-group-and-number)))
+    (prog1
+       (nntp-find-group-and-number)
+      (nntp-decode-text))))
 
 (deffoo nntp-request-body (article &optional group server)
   (nntp-possibly-change-group group server)
@@ -384,7 +634,10 @@ server there that you can connect to.  See also `nntp-open-connection-function'"
     (while (setq process (car (pop nntp-connection-alist)))
       (when (memq (process-status process) '(open run))
        (set-process-sentinel process nil)
-       (nntp-send-string process "QUIT"))
+       (ignore-errors
+         (nntp-send-string process "QUIT")
+         (unless (eq nntp-open-connection-function 'nntp-open-network-stream)
+           (sleep-for 1))))
       (when (buffer-name (process-buffer process))
        (kill-buffer (process-buffer process))))
     (nnoo-close-server 'nntp)))
@@ -394,7 +647,10 @@ server there that you can connect to.  See also `nntp-open-connection-function'"
     (while (setq process (pop nntp-connection-list))
       (when (memq (process-status process) '(open run))
        (set-process-sentinel process nil)
-       (nntp-send-string process "QUIT"))
+       (ignore-errors
+         (nntp-send-string process "QUIT")
+         (unless (eq nntp-open-connection-function 'nntp-open-network-stream)
+           (sleep-for 1))))
       (when (buffer-name (process-buffer process))
        (kill-buffer (process-buffer process))))))
 
@@ -413,9 +669,9 @@ server there that you can connect to.  See also `nntp-open-connection-function'"
     (let* ((date (timezone-parse-date date))
           (time-string
            (format "%s%02d%02d %s%s%s"
-                   (substring (aref date 0) 2) (string-to-int (aref date 1)) 
+                   (substring (aref date 0) 2) (string-to-int (aref date 1))
                    (string-to-int (aref date 2)) (substring (aref date 3) 0 2)
-                   (substring 
+                   (substring
                     (aref date 3) 3 5) (substring (aref date 3) 6 8))))
       (prog1
          (nntp-send-command "^\\.\r?\n" "NEWGROUPS" time-string)
@@ -428,7 +684,7 @@ server there that you can connect to.  See also `nntp-open-connection-function'"
 
 (deffoo nntp-request-type (group article)
   'news)
-  
+
 (deffoo nntp-asynchronous-p ()
   t)
 
@@ -445,111 +701,40 @@ reading."
   "Send the AUTHINFO to the nntp server.
 This function is supposed to be called from `nntp-server-opened-hook'.
 It will prompt for a password."
-  (nntp-send-command "^.*\r?\n" "AUTHINFO USER"
-                    (read-string "NNTP user name: "))
-  (nntp-send-command "^.*\r?\n" "AUTHINFO PASS" 
-                    (nnmail-read-passwd "NNTP password: ")))
+  (nntp-send-command
+   "^.*\r?\n" "AUTHINFO USER"
+   (read-string (format "NNTP (%s) user name: " nntp-address)))
+  (nntp-send-command
+   "^.*\r?\n" "AUTHINFO PASS"
+   (nnmail-read-passwd "NNTP (%s) password: " nntp-address)))
 
 (defun nntp-send-authinfo ()
   "Send the AUTHINFO to the nntp server.
 This function is supposed to be called from `nntp-server-opened-hook'.
 It will prompt for a password."
   (nntp-send-command "^.*\r?\n" "AUTHINFO USER" (user-login-name))
-  (nntp-send-command "^.*\r?\n" "AUTHINFO PASS" 
-                    (read-string "NNTP password: ")))
+  (nntp-send-command
+   "^.*\r?\n" "AUTHINFO PASS"
+   (nnmail-read-passwd (format "NNTP (%s) password: " nntp-address))))
 
 (defun nntp-send-authinfo-from-file ()
   "Send the AUTHINFO to the nntp server.
-This function is supposed to be called from `nntp-server-opened-hook'.
-It will prompt for a password."
+This function is supposed to be called from `nntp-server-opened-hook'."
   (when (file-exists-p "~/.nntp-authinfo")
-    (save-excursion
-      (set-buffer (get-buffer-create " *authinfo*"))
-      (buffer-disable-undo (current-buffer))
-      (erase-buffer)
+    (nnheader-temp-write nil
       (insert-file-contents "~/.nntp-authinfo")
       (goto-char (point-min))
       (nntp-send-command "^.*\r?\n" "AUTHINFO USER" (user-login-name))
-      (nntp-send-command 
-       "^.*\r?\n" "AUTHINFO PASS" 
-       (buffer-substring (point) (progn (end-of-line) (point))))
-      (kill-buffer (current-buffer)))))
+      (nntp-send-command
+       "^.*\r?\n" "AUTHINFO PASS"
+       (buffer-substring (point) (progn (end-of-line) (point)))))))
 
 ;;; Internal functions.
 
-(defun nntp-send-command (wait-for &rest strings)
-  "Send STRINGS to server and wait until WAIT-FOR returns."
-  (unless nnheader-callback-function
-    (save-excursion
-      (set-buffer nntp-server-buffer)
-      (erase-buffer)))
-  (nntp-retrieve-data
-   (mapconcat 'identity strings " ") 
-   nntp-address nntp-port-number nntp-server-buffer
-   wait-for nnheader-callback-function))
-
-(defun nntp-send-command-nodelete (wait-for &rest strings)
-  "Send STRINGS to server and wait until WAIT-FOR returns."
-  (nntp-retrieve-data
-   (mapconcat 'identity strings " ") 
-   nntp-address nntp-port-number nntp-server-buffer
-   wait-for nnheader-callback-function))
-
-(defun nntp-send-command-and-decode (wait-for &rest strings)
-  "Send STRINGS to server and wait until WAIT-FOR returns."
-  (unless nnheader-callback-function
-    (save-excursion
-      (set-buffer nntp-server-buffer)
-      (erase-buffer)))
-  (nntp-retrieve-data
-   (mapconcat 'identity strings " ") 
-   nntp-address nntp-port-number nntp-server-buffer
-   wait-for nnheader-callback-function t))
-
-(defun nntp-send-buffer (wait-for)
-  "Send the current buffer to server and wait until WAIT-FOR returns."
-  (unless nnheader-callback-function
-    (save-excursion
-      (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
-      (erase-buffer)))
-  (nntp-encode-text)
-  (process-send-region (nntp-find-connection nntp-server-buffer)
-                      (point-min) (point-max))
-  (nntp-retrieve-data
-   nil nntp-address nntp-port-number nntp-server-buffer
-   wait-for nnheader-callback-function))
-
-(defun nntp-find-connection (buffer)
-  "Find the connection delivering to BUFFER."
-  (let ((alist nntp-connection-alist)
-       (buffer (if (stringp buffer) (get-buffer buffer) buffer))
-       process entry)
-    (while (setq entry (pop alist))
-      (when (eq buffer (cadr entry))
-       (setq process (car entry)
-             alist nil)))
-    (when process
-      (if (memq (process-status process) '(open run))
-         process
-       (when (buffer-name (process-buffer process))
-         (kill-buffer (process-buffer process)))
-       (setq nntp-connection-alist (delq entry nntp-connection-alist))
-       nil))))
-
-(defun nntp-find-connection-entry (buffer)
-  "Return the entry for the connection to BUFFER."
-  (assq (nntp-find-connection buffer) nntp-connection-alist))
-
-(defun nntp-find-connection-buffer (buffer)
-  "Return the process connection buffer tied to BUFFER."
-  (let ((process (nntp-find-connection buffer)))
-    (when process
-      (process-buffer process))))
-
 (defun nntp-make-process-buffer (buffer)
   "Create a new, fresh buffer usable for nntp process connections."
   (save-excursion
-    (set-buffer 
+    (set-buffer
      (generate-new-buffer
       (format " *server %s %s %s*"
              nntp-address nntp-port-number
@@ -569,21 +754,24 @@ It will prompt for a password."
   (let* ((pbuffer (nntp-make-process-buffer buffer))
         (process
          (condition-case ()
-             (funcall
-              nntp-open-connection-function pbuffer)
-           (error nil))))
+             (let ((coding-system-for-read nntp-coding-system-for-read))
+               (funcall nntp-open-connection-function pbuffer))
+           (error nil)
+           (quit nil))))
     (when process
       (process-kill-without-query process)
-      (nntp-wait-for process "^.*\n" buffer)
+      (nntp-wait-for process "^.*\n" buffer nil t)
       (if (memq (process-status process) '(open run))
          (prog1
-             (caar (push (list process buffer nil) 
-                         nntp-connection-alist))
+             (caar (push (list process buffer nil) nntp-connection-alist))
            (push process nntp-connection-list)
            (save-excursion
-             (set-buffer nntp-server-buffer)
+             (set-buffer pbuffer)
              (nntp-read-server-type)
-             (run-hooks 'nntp-server-opened-hook)))
+             (erase-buffer)
+             (set-buffer nntp-server-buffer)
+             (let ((nnheader-callback-function nil))
+               (run-hooks 'nntp-server-opened-hook))))
        (when (buffer-name (process-buffer process))
          (kill-buffer (process-buffer process)))
        nil))))
@@ -596,6 +784,7 @@ It will prompt for a password."
   ;; Wait for the status string to arrive.
   (setq nntp-server-type (buffer-string))
   (let ((alist nntp-server-action-alist)
+       (case-fold-search t)
        entry)
     ;; Run server-specific commands.
     (while alist
@@ -632,94 +821,33 @@ It will prompt for a password."
                  (insert-buffer-substring cur start)
                  (narrow-to-region b (point-max))
                  (nntp-decode-text)
-                 (goto-char (point-min))
-                 (gnus-delete-line)
                  (widen)))))
          (goto-char end)
          (let ((callback nntp-process-callback)
                (nntp-inside-change-function t))
            (setq nntp-process-callback nil)
            (save-excursion
-             (funcall callback t))))))))
-
-(defun nntp-retrieve-data (command address port buffer
-                                  &optional wait-for callback decode)
-  "Use COMMAND to retrieve data into BUFFER from PORT on ADDRESS."
-  (let ((process (or (nntp-find-connection buffer)
-                    (nntp-open-connection buffer))))
-    (if (not process)
-       (nnheader-report 'nntp "Couldn't open connection to %s" address)
-      (unless (or nntp-inhibit-erase nnheader-callback-function)
-       (save-excursion
-         (set-buffer (process-buffer process))
-         (erase-buffer)))
-      (when command
-       (nntp-send-string process command))
-      (cond 
-       ((eq callback 'ignore)
-       t)
-       ((and callback wait-for)
-       (save-excursion
-         (set-buffer (process-buffer process))
-         (unless nntp-inside-change-function 
-           (erase-buffer))
-         (setq nntp-process-decode decode
-               nntp-process-to-buffer buffer
-               nntp-process-wait-for wait-for
-               nntp-process-callback callback
-               nntp-process-start-point (point-max)
-               after-change-functions 
-               (list 'nntp-after-change-function-callback)))
-       t)
-       (wait-for 
-       (nntp-wait-for process wait-for buffer decode))
-       (t t)))))
-
-(defun nntp-send-string (process string)
-  "Send STRING to PROCESS."
-  (process-send-string process (concat string nntp-end-of-line)))
-
-(defun nntp-wait-for (process wait-for buffer &optional decode)
-  "Wait for WAIT-FOR to arrive from PROCESS."
-  (save-excursion
-    (set-buffer (process-buffer process))
-    (goto-char (point-min))
-    (while (or (not (memq (following-char) '(?2 ?3 ?4 ?5)))
-              (looking-at "480"))
-      (when (looking-at "480")
-       (erase-buffer)
-       (funcall nntp-authinfo-function))
-      (nntp-accept-process-output process)
-      (goto-char (point-min)))
-    (prog1
-       (if (looking-at "[45]")
-           (progn
-             (nntp-snarf-error-message)
-             nil)
-         (goto-char (point-max))
-         (while (not (re-search-backward wait-for nil t))
-           (nntp-accept-process-output process)
-           (goto-char (point-max)))
-         (nntp-decode-text (not decode))
-         (save-excursion
-           (set-buffer buffer)
-           (goto-char (point-max))
-           (insert-buffer-substring (process-buffer process))
-           t))
-      (erase-buffer))))
+             (funcall callback (buffer-name
+                                (get-buffer nntp-process-to-buffer))))))))))
 
 (defun nntp-snarf-error-message ()
   "Save the error message in the current buffer."
-  (setq nntp-status-string (buffer-string)))
+  (let ((message (buffer-string)))
+    (while (string-match "[\r\n]+" message)
+      (setq message (replace-match " " t t message)))
+    (nnheader-report 'nntp message)
+    message))
 
 (defun nntp-accept-process-output (process)
   "Wait for output from PROCESS and message some dots."
   (save-excursion
     (set-buffer (or (nntp-find-connection-buffer nntp-server-buffer)
                    nntp-server-buffer))
-    (let ((len (/ (point-max) 10000)))
-      (unless (zerop len)
-       (message "nntp reading%s" (make-string len ?.))))
+    (let ((len (/ (point-max) 1024))
+         message-log-max)
+      (unless (< len 10)
+       (setq nntp-have-messaged t)
+       (nnheader-message 7 "nntp read: %dk" len)))
     (accept-process-output process 1)))
 
 (defun nntp-accept-response ()
@@ -727,18 +855,25 @@ It will prompt for a password."
   (nntp-accept-process-output (nntp-find-connection nntp-server-buffer)))
 
 (defun nntp-possibly-change-group (group server &optional connectionless)
-  (when server
-    (or (nntp-server-opened server)
-       (nntp-open-server server nil connectionless)))
+  (let ((nnheader-callback-function nil))
+    (when server
+      (or (nntp-server-opened server)
+         (nntp-open-server server nil connectionless)))
 
-  (unless connectionless
-    (or (nntp-find-connection nntp-server-buffer)
-       (nntp-open-connection nntp-server-buffer)))
+    (unless connectionless
+      (or (nntp-find-connection nntp-server-buffer)
+         (nntp-open-connection nntp-server-buffer))))
 
   (when group
     (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
       (when (not (equal group (caddr entry)))
-       (nntp-request-group group)))))
+       (save-excursion
+         (set-buffer (process-buffer (car entry)))
+         (erase-buffer)
+         (nntp-send-string (car entry) (concat "GROUP " group))
+         (nntp-wait-for-string "^2.*\n")
+         (setcar (cddr entry) group)
+         (erase-buffer))))))
 
 (defun nntp-decode-text (&optional cr-only)
   "Decode the text in the current buffer."
@@ -746,12 +881,15 @@ It will prompt for a password."
   (while (search-forward "\r" nil t)
     (delete-char -1))
   (unless cr-only
+    ;; Remove trailing ".\n" end-of-transfer marker.
     (goto-char (point-max))
     (forward-line -1)
     (when (looking-at ".\n")
       (delete-char 2))
+    ;; Delete status line.
     (goto-char (point-min))
     (delete-region (point) (progn (forward-line 1) (point)))
+    ;; Remove "." -> ".." encoding.
     (while (search-forward "\n.." nil t)
       (delete-char -1))))
 
@@ -768,12 +906,17 @@ It will prompt for a password."
       (insert "\n"))
     ;; Insert `.' at end of buffer (end of text mark).
     (goto-char (point-max))
-    (insert "." nntp-end-of-line)))
+    (insert ".\n")
+    (goto-char (point-min))
+    (while (not (eobp))
+      (end-of-line)
+      (insert "\r")
+      (forward-line 1))))
 
 (defun nntp-retrieve-headers-with-xover (articles &optional fetch-old)
   (set-buffer nntp-server-buffer)
   (erase-buffer)
-  (cond 
+  (cond
 
    ;; This server does not talk NOV.
    ((not nntp-server-xover)
@@ -782,10 +925,10 @@ It will prompt for a password."
    ;; We don't care about gaps.
    ((or (not nntp-nov-gap)
        fetch-old)
-    (nntp-send-xover-command 
+    (nntp-send-xover-command
      (if fetch-old
-        (if (numberp fetch-old) 
-            (max 1 (- (car articles) fetch-old)) 
+        (if (numberp fetch-old)
+            (max 1 (- (car articles) fetch-old))
           1)
        (car articles))
      (car (last articles)) 'wait)
@@ -818,8 +961,8 @@ It will prompt for a password."
       (while (and nntp-server-xover articles)
        (setq first (car articles))
        ;; Search forward until we find a gap, or until we run out of
-       ;; articles. 
-       (while (and (cdr articles) 
+       ;; articles.
+       (while (and (cdr articles)
                    (< (- (nth 1 articles) (car articles)) nntp-nov-gap))
          (setq articles (cdr articles)))
 
@@ -835,8 +978,8 @@ It will prompt for a password."
            ;; On some Emacs versions the preceding function has
            ;; a tendency to change the buffer.  Perhaps.  It's
            ;; quite difficult to reproduce, because it only
-           ;; seems to happen once in a blue moon. 
-           (set-buffer buf) 
+           ;; seems to happen once in a blue moon.
+           (set-buffer buf)
            (while (progn
                     (goto-char last-point)
                     ;; Count replies.
@@ -857,7 +1000,7 @@ It will prompt for a password."
                   (forward-line -1)
                   (not (looking-at "^\\.\r?\n")))
            (nntp-accept-response)))
-       
+
        ;; We remove any "." lines and status lines.
        (goto-char (point-min))
        (while (search-forward "\r" nil t)
@@ -877,12 +1020,13 @@ It will prompt for a password."
        ;; If `nntp-server-xover' is a string, then we just send this
        ;; command.
        (if wait-for-reply
-           (nntp-send-command-nodelete "\r?\n\\.\r?\n" nntp-server-xover range)
+           (nntp-send-command-nodelete
+            "\r?\n\\.\r?\n" nntp-server-xover range)
          ;; We do not wait for the reply.
          (nntp-send-command-nodelete "\r?\n\\.\r?\n" nntp-server-xover range))
       (let ((commands nntp-xover-commands))
        ;; `nntp-xover-commands' is a list of possible XOVER commands.
-       ;; We try them all until we get at positive response. 
+       ;; We try them all until we get at positive response.
        (while (and commands (eq nntp-server-xover 'try))
          (nntp-send-command-nodelete "\r?\n\\.\r?\n" (car commands) range)
          (save-excursion
@@ -920,8 +1064,9 @@ It will prompt for a password."
   (save-excursion
     (set-buffer buffer)
     (erase-buffer)
-    (let ((proc (start-process
-                "nntpd" buffer "telnet" "-8"))
+    (let ((proc (apply
+                'start-process
+                "nntpd" buffer nntp-telnet-command nntp-telnet-switches))
          (case-fold-search t))
       (when (memq (process-status proc) '(open run))
        (process-send-string proc "set escape \^X\n")
@@ -943,7 +1088,7 @@ It will prompt for a password."
        (nntp-wait-for-string "bash\\|\$ *\r?$\\|> *\r?")
        (process-send-string
         proc (concat (mapconcat 'identity nntp-telnet-parameters " ") "\n"))
-       (nntp-wait-for-string "^\r*200")
+       (nntp-wait-for-string "^\r*20[01]")
        (beginning-of-line)
        (delete-region (point-min) (point))
        (process-send-string proc "\^]")
@@ -959,21 +1104,18 @@ It will prompt for a password."
 (defun nntp-open-rlogin (buffer)
   "Open a connection to SERVER using rsh."
   (let ((proc (if nntp-rlogin-user-name
-                 (start-process
-                  "nntpd" buffer "rsh"
-                  nntp-address "-l" nntp-rlogin-user-name
-                  (mapconcat 'identity
-                             nntp-rlogin-parameters " "))
-               (start-process
-                "nntpd" buffer "rsh" nntp-address
-                (mapconcat 'identity
-                           nntp-rlogin-parameters " ")))))
+                 (apply 'start-process
+                        "nntpd" buffer nntp-rlogin-program
+                        nntp-address "-l" nntp-rlogin-user-name
+                        nntp-rlogin-parameters)
+               (apply 'start-process
+                      "nntpd" buffer nntp-rlogin-program nntp-address
+                      nntp-rlogin-parameters))))
     (set-buffer buffer)
-    (nntp-wait-for-string "^\r*200")
+    (nntp-wait-for-string "^\r*20[01]")
     (beginning-of-line)
     (delete-region (point-min) (point))
-    proc)
-  )
+    proc))
 
 (defun nntp-find-group-and-number ()
   (save-excursion
@@ -991,7 +1133,7 @@ It will prompt for a password."
        (and number (zerop number) (setq number nil))
        ;; Then we find the group name.
        (setq group
-             (cond 
+             (cond
               ;; If there is only one group in the Newsgroups header,
               ;; then it seems quite likely that this article comes
               ;; from that group, I'd say.
@@ -1004,13 +1146,13 @@ It will prompt for a password."
               ;; article number in the Xref header is the one we are
               ;; looking for.  This might very well be wrong if this
               ;; article happens to have the same number in several
-              ;; groups, but that's life. 
+              ;; groups, but that's life.
               ((and (setq xref (mail-fetch-field "xref"))
                     number
                     (string-match (format "\\([^ :]+\\):%d" number) xref))
                (substring xref (match-beginning 1) (match-end 1)))
               (t "")))
-       (when (string-match "\r" group) 
+       (when (string-match "\r" group)
          (setq group (substring group 0 (match-beginning 0))))
        (cons group number)))))