*** empty log message ***
[gnus] / lisp / nntp.el
index a14cd9c..0c763c4 100644 (file)
@@ -1,4 +1,4 @@
-;;; nntp.el --- NNTP (RFC977) Interface for GNU Emacs
+;;; nntp.el --- nntp access for Gnus
 ;; Copyright (C) 1987,88,89,90,92,93,94,95 Free Software Foundation, Inc.
 
 ;; Author: Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
 ;;; Code:
 
 (require 'rnews)
+(require 'sendmail)
 (require 'nnheader)
 
+(eval-when-compile (require 'cl))
+
 (eval-and-compile
   (autoload 'news-setup "rnewspost")
   (autoload 'news-reply-mode "rnewspost")
-  (autoload 'nnmail-request-post-buffer "nnmail")
-  (autoload 'cancel-timer "timer"))
+  (autoload 'cancel-timer "timer")
+  (autoload 'telnet "telnet" nil t)
+  (autoload 'telnet-send-input "telnet" nil t)
+  (autoload 'timezone-parse-date "timezone"))
 
 (defvar nntp-server-hook nil
   "*Hooks for the NNTP server.
@@ -52,12 +57,12 @@ hook, use the variable `nntp-address'.")
 
 (defvar nntp-server-opened-hook nil
   "*Hook used for sending commands to the server at startup.  
-The default value is `nntp-send-mode-reader', whick makes an innd
+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.")  (add-hook
-'nntp-server-opened-hook 'nntp-send-mode-reader)
+do on servers that use strict access control.")  
+(add-hook 'nntp-server-opened-hook 'nntp-send-mode-reader)
 
 (defvar nntp-open-server-function 'nntp-open-network-stream
   "*Function used for connecting to a remote system.
@@ -113,6 +118,11 @@ 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.")
 
+(defvar nntp-nov-gap 20
+  "*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.")
+
 (defvar nntp-connection-timeout nil
   "*Number of seconds to wait before an nntp connection times out.
 If this variable is nil, which is the default, no timers are set.")
@@ -128,6 +138,12 @@ access to an NNTP server that you can't access locally.  You could
 then use this hook to rsh to the remote machine and start a proxy NNTP
 server there that you can connect to.")
 
+(defvar nntp-async-number 5
+  "*How many articles should be prefetched when in asynchronous mode.")
+
+(defvar nntp-warn-about-losing-connection t
+  "*If non-nil, beep when a server closes connection.")
+
 \f
 
 (defconst nntp-version "nntp 4.0"
@@ -146,164 +162,190 @@ instead use `nntp-server-buffer'.")
 You'd better not use this variable in NNTP front-end program but
 instead call function `nntp-status-message' to get status message.")
 
-(defvar nntp-server-xover t)
+(defvar nntp-opened-connections nil
+  "All (possibly) opened connections.")
+
+(defvar nntp-server-xover 'try)
 (defvar nntp-server-list-active-group 'try)
 (defvar nntp-current-group "")
 
+(defvar nntp-async-process nil)
+(defvar nntp-async-buffer nil)
+(defvar nntp-async-articles nil)
+(defvar nntp-async-fetched nil)
+(defvar nntp-async-group-alist nil)
+
+
 \f
 (defvar nntp-current-server nil)
 (defvar nntp-server-alist nil)
 (defvar nntp-server-variables 
-  (list
-   (list 'nntp-server-hook nntp-server-hook)
-   (list 'nntp-server-opened-hook nntp-server-opened-hook)
-   (list 'nntp-port-number nntp-port-number)
-   (list 'nntp-address nntp-address)
-   (list 'nntp-large-newsgroup nntp-large-newsgroup)
-   (list 'nntp-buggy-select nntp-buggy-select)
-   (list 'nntp-maximum-request nntp-maximum-request)
-   (list 'nntp-debug-read nntp-debug-read)
-   (list 'nntp-nov-is-evil nntp-nov-is-evil)
-   (list 'nntp-xover-commands nntp-xover-commands)
-   (list 'nntp-connection-timeout nntp-connection-timeout)
-   (list 'nntp-news-default-headers nntp-news-default-headers)
-   (list 'nntp-prepare-server-hook nntp-prepare-server-hook) 
-   '(nntp-server-process nil)
-   '(nntp-status-string nil)
-   '(nntp-server-xover t)
-   '(nntp-server-list-active-group 'try)
-   '(nntp-current-group "")))
+  `((nntp-server-hook ,nntp-server-hook)
+    (nntp-server-opened-hook ,nntp-server-opened-hook)
+    (nntp-port-number ,nntp-port-number)
+    (nntp-address ,nntp-address)
+    (nntp-large-newsgroup ,nntp-large-newsgroup)
+    (nntp-buggy-select ,nntp-buggy-select)
+    (nntp-maximum-request ,nntp-maximum-request)
+    (nntp-debug-read ,nntp-debug-read)
+    (nntp-nov-is-evil ,nntp-nov-is-evil)
+    (nntp-xover-commands ,nntp-xover-commands)
+    (nntp-connection-timeout ,nntp-connection-timeout)
+    (nntp-news-default-headers ,nntp-news-default-headers)
+    (nntp-prepare-server-hook ,nntp-prepare-server-hook) 
+    (nntp-async-number ,nntp-async-number)
+    (nntp-async-process nil)
+    (nntp-async-buffer nil)
+    (nntp-async-articles nil)
+    (nntp-async-fetched nil)
+    (nntp-async-group-alist nil)
+    (nntp-server-process nil)
+    (nntp-status-string nil)
+    (nntp-server-xover try)
+    (nntp-server-list-active-group try)
+    (nntp-current-group "")))
 
 \f
-;;; Interface funtions.
+;;; Interface functions.
 
-(defun nntp-retrieve-headers (sequence &optional newsgroup server)
-  "Retrieve the headers to the articles in SEQUENCE."
-  (nntp-possibly-change-server newsgroup server)
+(defun nntp-retrieve-headers (articles &optional group server fetch-old)
+  "Retrieve the headers of ARTICLES."
+  (nntp-possibly-change-server group server)
   (save-excursion
     (set-buffer nntp-server-buffer)
     (erase-buffer)
     (if (and (not gnus-nov-is-evil) 
             (not nntp-nov-is-evil)
-            (nntp-retrieve-headers-with-xover sequence))
+            (nntp-retrieve-headers-with-xover articles fetch-old))
+       ;; We successfully retrieved the headers via XOVER.
         'nov
-      (let ((number (length sequence))
+      ;; XOVER didn't work, so we do it the hard, slow and inefficient
+      ;; way.  
+      (let ((number (length articles))
            (count 0)
            (received 0)
            (last-point (point-min)))
        ;; Send HEAD command.
-       (while sequence
-         (nntp-send-strings-to-server "HEAD" (car sequence))
-         (setq sequence (cdr sequence))
-         (setq count (1+ count))
-         ;; Every 400 header requests we have to read stream in order
-         ;;  to avoid deadlock.
-         (if (or (null sequence)       ;All requests have been sent.
-                 (zerop (% count nntp-maximum-request)))
-             (progn
-               (accept-process-output)
-               (while (progn
-                        (goto-char last-point)
-                        ;; Count replies.
-                        (while (re-search-forward "^[0-9]" nil t)
-                          (setq received (1+ 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)))))
+       (while articles
+         (nntp-send-strings-to-server 
+          "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
+                    (goto-char last-point)
+                    ;; Count replies.
+                    (while (re-search-forward "^[0-9]" nil t)
+                      (setq received (1+ 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)
-       (if (looking-at "^[23]")
-           (while (progn
-                    (goto-char (- (point-max) 3))
-                    (not (looking-at "^\\.\r$")))
-             (nntp-accept-response)))
+       (when (looking-at "^[23]")
+         (while (progn
+                  (goto-char (- (point-max) 3))
+                  (not (looking-at "^\\.\r?\n")))
+           (nntp-accept-response)))
        (and (numberp nntp-large-newsgroup)
             (> number nntp-large-newsgroup)
-            (message "NNTP: Receiving headers... done"))
+            (message "NNTP: Receiving headers...done"))
 
-       ;; Now all of replies are received.
-       (setq received number)
-       ;; First, fold continuation lines.
+       ;; Now all of replies are received.  Fold continuation lines.
        (goto-char (point-min))
        (while (re-search-forward "\\(\r?\n[ \t]+\\)+" nil t)
-         (replace-match " "))
-       ;; Remove all "\r"'s
+         (replace-match " " t t))
+       ;; Remove all "\r"'s.
        (goto-char (point-min))
        (while (search-forward "\r" nil t)
-         (replace-match ""))
+         (replace-match "" t t))
        'headers))))
 
 
 (defun nntp-retrieve-groups (groups &optional server)
+  "Retrieve group info on GROUPS."
   (nntp-possibly-change-server nil server)
   (save-excursion
     (set-buffer nntp-server-buffer)
-    (and (eq nntp-server-list-active-group 'try)
-        (nntp-try-list-active (car groups)))
+    ;; The first time this is run, this variable is `try'.  So we
+    ;; try.   
+    (when (eq nntp-server-list-active-group 'try)
+      (nntp-try-list-active (car groups)))
     (erase-buffer)
     (let ((count 0)
          (received 0)
          (last-point (point-min))
-         (command (if nntp-server-list-active-group
-                      "LIST ACTIVE" "GROUP")))
-       (while groups
-         (nntp-send-strings-to-server "HEAD" (car groups))
-         (setq groups (cdr groups))
-         (setq count (1+ count))
-         ;; Every 400 requests we have to read the stream in
-         ;; order to avoid deadlocks.
-         (if (or (null groups)       ;All requests have been sent.
+         (command (if nntp-server-list-active-group "LIST ACTIVE" "GROUP")))
+      (while groups
+       ;; Send the command to the server.
+       (nntp-send-strings-to-server command (car groups))
+       (setq groups (cdr groups))
+       (setq count (1+ count))
+       ;; Every 400 requests we have to read the stream in
+       ;; order to avoid deadlocks.
+       (when (or (null groups)         ;All requests have been sent.
                  (zerop (% count nntp-maximum-request)))
-             (progn
-               (accept-process-output)
-               (while (progn
-                        (goto-char last-point)
-                        ;; Count replies.
-                        (while (re-search-forward "^[0-9]" nil t)
-                          (setq received (1+ received)))
-                        (setq last-point (point))
-                        (< received count))
-                 (nntp-accept-response)))))
-       ;; Wait for the reply from the final command.
+         (nntp-accept-response)
+         (while (progn
+                  (goto-char last-point)
+                  ;; Count replies.
+                  (while (re-search-forward "^[0-9]" nil t)
+                    (setq received (1+ received)))
+                  (setq last-point (point))
+                  (< received count))
+           (nntp-accept-response))))
+
+      ;; Wait for the reply from the final command.
+      (when nntp-server-list-active-group
        (goto-char (point-max))
        (re-search-backward "^[0-9]" nil t)
-       (if (looking-at "^[23]")
-           (while (progn
-                    (goto-char (- (point-max) 3))
-                    (not (looking-at "^\\.\r$")))
-             (nntp-accept-response)))
+       (when (looking-at "^[23]")
+         (while (progn
+                  (goto-char (- (point-max) 3))
+                  (not (looking-at "^\\.\r?\n")))
+           (nntp-accept-response))))
 
-       ;; Now all replies are received. We remove CRs.
-       (goto-char (point-min))
-       (while (search-forward "\r" nil t)
-         (replace-match ""))
+      ;; Now all replies are received. We remove CRs.
+      (goto-char (point-min))
+      (while (search-forward "\r" nil t)
+       (replace-match "" t t))
 
-       (if nntp-server-list-active-group
-           (progn
-             ;; We have read active entries, so we just delete the
-             ;; superfluos gunk.
-             (goto-char (point-min))
-             (while (re-search-forward "^[\\.234]" nil t)
-               (delete-region (match-beginning 0) 
-                              (progn (forward-line 1) (point))))
-             'active)
-         'group))))
-
-(defun nntp-open-server (server &optional defs)
+      (if (not nntp-server-list-active-group)
+         'group
+       ;; We have read active entries, so we just delete the
+       ;; superfluos gunk.
+       (goto-char (point-min))
+       (while (re-search-forward "^[.2-5]" nil t)
+         (delete-region (match-beginning 0) 
+                        (progn (forward-line 1) (point))))
+       'active))))
+
+(defun nntp-open-server (server &optional defs connectionless)
+  "Open the virtual server SERVER.
+If CONNECTIONLESS is non-nil, don't attempt to connect to any physical
+servers."
   (nnheader-init-server-buffer)
   (if (nntp-server-opened server)
       t
     (if (or (stringp (car defs))
            (numberp (car defs)))
-       (setq defs (cons (list 'nntp-server-port (car defs)) (cdr defs))))
+       (setq defs (cons (list 'nntp-port-number (car defs)) (cdr defs))))
     (or (assq 'nntp-address defs)
        (setq defs (append defs (list (list 'nntp-address server)))))
     (if (and nntp-current-server
@@ -319,12 +361,15 @@ instead call function `nntp-status-message' to get status message.")
            (setq nntp-server-alist (delq state nntp-server-alist)))
        (nnheader-set-init-variables nntp-server-variables defs)))
     (setq nntp-current-server server)
-    (run-hooks 'nntp-prepare-server-hook)
-    (nntp-open-server-semi-internal nntp-address)))
+    (or (nntp-server-opened server)
+       connectionless
+       (progn
+         (run-hooks 'nntp-prepare-server-hook)
+         (nntp-open-server-semi-internal nntp-address nntp-port-number)))))
 
 (defun nntp-close-server (&optional server)
   "Close connection to SERVER."
-  (nntp-possibly-change-server nil server)
+  (nntp-possibly-change-server nil server t)
   (unwind-protect
       (progn
        ;; Un-set default sentinel function before closing connection.
@@ -337,16 +382,28 @@ instead call function `nntp-status-message' to get status message.")
            (nntp-send-command nil "QUIT")))
     (nntp-close-server-internal server)))
 
-(fset 'nntp-request-quit (symbol-function 'nntp-close-server))
+(defalias 'nntp-request-quit (symbol-function 'nntp-close-server))
 
 (defun nntp-request-close ()
   "Close all server connections."
   (let (proc)
+     (while nntp-opened-connections
+       (when (setq proc (pop nntp-opened-connections))
+        (condition-case ()
+            (process-send-string proc "QUIT\n")
+          (error nil))
+        (delete-process proc)))
+     (and nntp-async-buffer
+         (get-buffer nntp-async-buffer)
+         (kill-buffer nntp-async-buffer))
     (while nntp-server-alist
-      (setq proc (nth 1 (assq 'nntp-server-process (car nntp-server-alist))))
-      (and proc (delete-process proc))
+      (and (setq proc (nth 1 (assq 'nntp-async-buffer
+                                  (car nntp-server-alist))))
+          (buffer-name proc)
+          (kill-buffer proc))
       (setq nntp-server-alist (cdr nntp-server-alist)))
-    (setq nntp-current-server nil)))
+    (setq nntp-current-server nil
+         nntp-async-group-alist nil)))
 
 (defun nntp-server-opened (&optional server)
   "Say whether a connection to SERVER has been opened."
@@ -364,106 +421,150 @@ instead call function `nntp-status-message' to get status message.")
                         nntp-status-string))
       (substring nntp-status-string (match-beginning 1) (match-end 1))
     ;; Empty message if nothing.
-    nntp-status-string))
+    (or nntp-status-string "")))
 
-(defun nntp-request-article (id &optional newsgroup server buffer)
-  "Request article ID (message-id or number)."
-  (nntp-possibly-change-server newsgroup server)
-  (unwind-protect
-      (progn
-       (if buffer (set-process-buffer nntp-server-process buffer))
-       (let ((nntp-server-buffer (or buffer nntp-server-buffer))
-             (id (or (and (numberp id) (int-to-string id)) id)))
-         ;; If NEmacs, end of message may look like: "\256\215" (".^M")
-         (prog1
-             (nntp-send-command "^\\.\r$" "ARTICLE" id)
-           (nntp-decode-text))))
-    (if buffer (set-process-buffer nntp-server-process nntp-server-buffer))))
-
-(defun nntp-request-body (id &optional newsgroup server)
-  "Request body of article ID (message-id or number)."
-  (nntp-possibly-change-server newsgroup server)
+(defun nntp-request-article (id &optional group server buffer)
+  "Request article ID (Message-ID or number)."
+  (nntp-possibly-change-server group server)
+
+  (let (found)
+
+    ;; First we see whether we can get the article from the async buffer. 
+    (when (and (numberp id)
+              nntp-async-articles
+              (memq id nntp-async-fetched))
+      (save-excursion
+       (set-buffer nntp-async-buffer)
+       (let ((opoint (point))
+             (art (if (numberp id) (int-to-string id) id))
+             beg end)
+         (when (and (or (re-search-forward (concat "^2.. +" art) nil t)
+                        (progn
+                          (goto-char (point-min))
+                          (re-search-forward (concat "^2.. +" art) opoint t)))
+                    (progn
+                      (beginning-of-line)
+                      (setq beg (point)
+                            end (re-search-forward "^\\.\r?\n" nil t))))
+           (setq found t)
+           (save-excursion
+             (set-buffer (or buffer nntp-server-buffer))
+             (erase-buffer)
+             (insert-buffer-substring nntp-async-buffer beg end)
+             (let ((nntp-server-buffer (current-buffer)))
+               (nntp-decode-text)))
+           (delete-region beg end)
+           (when nntp-async-articles
+             (nntp-async-fetch-articles id))))))
+
+    (if found 
+       id
+      ;; The article was not in the async buffer, so we fetch it now.
+      (unwind-protect
+         (progn
+           (if buffer (set-process-buffer nntp-server-process buffer))
+           (let ((nntp-server-buffer (or buffer nntp-server-buffer))
+                 (art (or (and (numberp id) (int-to-string id)) id)))
+             (prog1
+                 (and (nntp-send-command "^\\.\r?\n" "ARTICLE" art)
+                      (if (numberp id) 
+                          (cons nntp-current-group id)
+                        ;; We find out what the article number was.
+                        (nntp-find-group-and-number)))
+               (nntp-decode-text)
+               (and nntp-async-articles (nntp-async-fetch-articles id)))))
+       (when buffer 
+         (set-process-buffer nntp-server-process nntp-server-buffer))))))
+
+(defun nntp-request-body (id &optional group server)
+  "Request body of article ID (Message-ID or number)."
+  (nntp-possibly-change-server group server)
   (prog1
       ;; If NEmacs, end of message may look like: "\256\215" (".^M")
       (nntp-send-command
-       "^\\.\r$" "BODY" (or (and (numberp id) (int-to-string id)) id))
+       "^\\.\r?\n" "BODY" (or (and (numberp id) (int-to-string id)) id))
     (nntp-decode-text)))
 
-(defun nntp-request-head (id &optional newsgroup server)
-  "Request head of article ID (message-id or number)."
-  (nntp-possibly-change-server newsgroup server)
+(defun nntp-request-head (id &optional group server)
+  "Request head of article ID (Message-ID or number)."
+  (nntp-possibly-change-server group server)
   (prog1
-      (nntp-send-command 
-       "^\\.\r$" "HEAD" (or (and (numberp id) (int-to-string id)) id))
+      (and (nntp-send-command 
+           "^\\.\r?\n" "HEAD" (if (numberp id) (int-to-string id) id))
+          (if (numberp id) id
+            ;; We find out what the article number was.
+            (nntp-find-group-and-number)))
     (nntp-decode-text)))
 
-(defun nntp-request-stat (id &optional newsgroup server)
-  "Request STAT of article ID (message-id or number)."
-  (nntp-possibly-change-server newsgroup server)
+(defun nntp-request-stat (id &optional group server)
+  "Request STAT of article ID (Message-ID or number)."
+  (nntp-possibly-change-server group server)
   (nntp-send-command 
-   "^[23].*\r$" "STAT" (or (and (numberp id) (int-to-string id)) id)))
+   "^[23].*\r?\n" "STAT" (or (and (numberp id) (int-to-string id)) id)))
 
 (defun nntp-request-group (group &optional server dont-check)
   "Select GROUP."
-  (if dont-check
-      (nntp-send-command "^.*\r$" "GROUP" group)
-    (cond ((eq nntp-server-list-active-group 'try)
-          (or (nntp-try-list-active group)
-              (nntp-send-command "^.*\r$" "GROUP" group)))
-         (nntp-server-list-active-group
-          (nntp-list-active-group group))
-         (t
-          (nntp-send-command "^.*\r$" "GROUP" group)))
-    (if nntp-server-list-active-group
-       (save-excursion
-         (set-buffer nntp-server-buffer)
-         (goto-char (point-min))
-         (forward-line 1)
-         (if (looking-at "[^ ]+[ \t]+\\([0-9]\\)[ \t]+\\([0-9]\\)")
-             (let ((end (progn (goto-char (match-beginning 1))
-                               (read (current-buffer))))
-                   (beg (read (current-buffer))))
-               (and (> beg end)
-                    (setq end 0
-                          beg 0))
-               (erase-buffer)
-               (insert (format "211 %s %d %d %d\n"
-                               group (max (- (1+ end) beg) 0)
-                               beg end))))))
-    (save-excursion
-      (set-buffer nntp-server-buffer)
-      (goto-char (point-min))
-      (looking-at "[23]"))))
+  (setq nntp-current-group
+       (when (nntp-send-command "^2.*\r?\n" "GROUP" group)
+         group)))
+
+(defun nntp-request-asynchronous (group &optional server articles)
+  "Enable pre-fetch in GROUP."
+  (when nntp-async-articles
+    (nntp-async-request-group group))
+  (when nntp-async-number
+    (if (not (or (nntp-async-server-opened)
+                (nntp-async-open-server)))
+       ;; Couldn't open the second connection
+       (progn
+         (message "Can't open second connection to %s" nntp-address)
+         (ding)
+         (setq nntp-async-articles nil)
+         (sit-for 2))
+      ;; We opened the second connection (or it was opened already).  
+      (setq nntp-async-articles articles)
+      (setq nntp-async-fetched nil)
+      ;; Clear any old data.
+      (save-excursion
+       (set-buffer nntp-async-buffer)
+       (erase-buffer))
+      ;; Select the correct current group on this server.
+      (nntp-async-send-strings "GROUP" group)
+      t)))
 
 (defun nntp-list-active-group (group &optional server)
-  (nntp-send-command "^.*\r$" "LIST ACTIVE" group))
+  "Return the active info on GROUP (which can be a regexp."
+  (nntp-possibly-change-server group server)
+  (nntp-send-command "^.*\r?\n" "LIST ACTIVE" group))
 
 (defun nntp-request-group-description (group &optional server)
-  "Get description of GROUP."
-  (if (nntp-possibly-change-server nil server)
-      (prog1
-         (nntp-send-command "^.*\r$" "XGTITLE" group)
-       (nntp-decode-text))))
+  "Get the description of GROUP."
+  (nntp-possibly-change-server nil server)
+  (prog1
+      (nntp-send-command "^.*\r?\n" "XGTITLE" group)
+    (nntp-decode-text)))
 
 (defun nntp-close-group (group &optional server)
+  "Close GROUP."
+  (setq nntp-current-group nil)
   t)
 
 (defun nntp-request-list (&optional server)
-  "List active groups."
+  "List all active groups."
   (nntp-possibly-change-server nil server)
   (prog1
-      (nntp-send-command "^\\.\r$" "LIST")
+      (nntp-send-command "^\\.\r?\n" "LIST")
     (nntp-decode-text)))
 
 (defun nntp-request-list-newsgroups (&optional server)
-  "List groups."
+  "Get descriptions on all groups on SERVER."
   (nntp-possibly-change-server nil server)
   (prog1
-      (nntp-send-command "^\\.\r$" "LIST NEWSGROUPS")
+      (nntp-send-command "^\\.\r?\n" "LIST NEWSGROUPS")
     (nntp-decode-text)))
 
 (defun nntp-request-newgroups (date &optional server)
-  "List new groups."
+  "List groups that have arrived since DATE."
   (nntp-possibly-change-server nil server)
   (let* ((date (timezone-parse-date date))
         (time-string
@@ -473,123 +574,35 @@ instead call function `nntp-status-message' to get status message.")
                  (substring 
                   (aref date 3) 3 5) (substring (aref date 3) 6 8))))
     (prog1
-       (nntp-send-command "^\\.\r$" "NEWGROUPS" time-string)
+       (nntp-send-command "^\\.\r?\n" "NEWGROUPS" time-string)
       (nntp-decode-text))))
 
 (defun nntp-request-list-distributions (&optional server)
   "List distributions."
   (nntp-possibly-change-server nil server)
   (prog1
-      (nntp-send-command "^\\.\r$" "LIST DISTRIBUTIONS")
+      (nntp-send-command "^\\.\r?\n" "LIST DISTRIBUTIONS")
     (nntp-decode-text)))
 
-(defun nntp-request-last (&optional newsgroup server)
+(defun nntp-request-last (&optional group server)
   "Decrease the current article pointer."
-  (nntp-possibly-change-server newsgroup server)
-  (nntp-send-command "^[23].*\r$" "LAST"))
+  (nntp-possibly-change-server group server)
+  (nntp-send-command "^[23].*\r?\n" "LAST"))
 
-(defun nntp-request-next (&optional newsgroup server)
+(defun nntp-request-next (&optional group server)
   "Advance the current article pointer."
-  (nntp-possibly-change-server newsgroup server)
-  (nntp-send-command "^[23].*\r$" "NEXT"))
+  (nntp-possibly-change-server group server)
+  (nntp-send-command "^[23].*\r?\n" "NEXT"))
 
 (defun nntp-request-post (&optional server)
   "Post the current buffer."
   (nntp-possibly-change-server nil server)
-  (if (nntp-send-command "^[23].*\r$" "POST")
-      (progn
-       (nntp-encode-text)
-       (nntp-send-region-to-server (point-min) (point-max))
-       ;; 1.2a NNTP's post command is buggy. "^M" (\r) is not
-       ;;  appended to end of the status message.
-       (nntp-wait-for-response "^[23].*$"))))
-
-(defun nntp-request-post-buffer 
-  (post group subject header article-buffer info follow-to respect-poster)
-  "Request a buffer suitable for composing an article.
-If POST, this is an original article; otherwise it's a followup.
-GROUP is the group to be posted to, the article should have subject
-SUBJECT.  HEADER is a Gnus header vector.  ARTICLE-BUFFER contains the
-article being followed up.  INFO is a Gnus info list.  If FOLLOW-TO,
-post to this group instead.  If RESPECT-POSTER, heed the special
-\"poster\" value of the Followup-to header."
-  (if (assq 'to-address (nth 4 info))
-      (nnmail-request-post-buffer 
-       post group subject header article-buffer info follow-to respect-poster)
-    (let ((mail-default-headers 
-          (or nntp-news-default-headers mail-default-headers))
-         from date to followup-to newsgroups message-of
-         references distribution message-id)
-      (save-excursion
-       (set-buffer (get-buffer-create "*post-news*"))
-       (news-reply-mode)
-       (if (and (buffer-modified-p)
-                (> (buffer-size) 0)
-                (not (y-or-n-p "Unsent article being composed; erase it? ")))
-           ()
-         (erase-buffer)
-         (if post
-             (news-setup nil subject nil group nil)
-           (save-excursion
-             (set-buffer article-buffer)
-             (goto-char (point-min))
-             (narrow-to-region (point-min)
-                               (progn (search-forward "\n\n") (point)))
-             (setq from (header-from header))
-             (setq date (header-date header))
-             (and from
-                  (let ((stop-pos 
-                         (string-match "  *at \\|  *@ \\| *(\\| *<" from)))
-                    (setq 
-                     message-of
-                     (concat (if stop-pos (substring from 0 stop-pos) from) 
-                             "'s message of " date))))
-             (setq subject (or subject (header-subject header)))
-             (or (string-match "^[Rr][Ee]:" subject)
-                 (setq subject (concat "Re: " subject)))
-             (setq followup-to (mail-fetch-field "followup-to"))
-             (if (or (null respect-poster) ;Ignore followup-to: field.
-                     (string-equal "" followup-to) ;Bogus header.
-                     (string-equal "poster" followup-to)) ;Poster
-                 (setq followup-to nil))
-             (setq newsgroups
-                   (or follow-to followup-to (mail-fetch-field "newsgroups")))
-             (setq references (header-references header))
-             (setq distribution (mail-fetch-field "distribution"))
-             ;; Remove bogus distribution.
-             (and (stringp distribution)
-                  (string-match "world" distribution)
-                  (setq distribution nil))
-             (setq message-id (header-id header))
-             (widen))
-           (setq news-reply-yank-from from)
-           (setq news-reply-yank-message-id message-id)
-           (news-setup to subject message-of 
-                       (if (stringp newsgroups) newsgroups "") 
-                       article-buffer)
-           (if (and newsgroups (listp newsgroups))
-               (progn
-                 (goto-char (point-min))
-                 (while newsgroups
-                   (insert (car (car newsgroups)) ": " 
-                           (cdr (car newsgroups)) "\n")
-                   (setq newsgroups (cdr newsgroups)))))
-           ;; Fold long references line to follow RFC1036.
-           (mail-position-on-field "References")
-           (let ((begin (- (point) (length "References: ")))
-                 (fill-column 79)
-                 (fill-prefix "\t"))
-             (if references (insert references))
-             (if (and references message-id) (insert " "))
-             (if message-id (insert message-id))
-             ;; The region must end with a newline to fill the region
-             ;; without inserting extra newline.
-             (fill-region-as-paragraph begin (1+ (point))))
-           (if distribution
-               (progn
-                 (mail-position-on-field "Distribution")
-                 (insert distribution)))))
-       (current-buffer)))))
+  (when (nntp-send-command "^[23].*\r?\n" "POST")
+    (nntp-encode-text)
+    (nntp-send-region-to-server (point-min) (point-max))
+    ;; 1.2a NNTP's post command is buggy. "^M" (\r) is not
+    ;;  appended to end of the status message.
+    (nntp-wait-for-response "^[23].*\n")))
 
 ;;; Internal functions.
 
@@ -598,37 +611,72 @@ post to this group instead.  If RESPECT-POSTER, heed the special
 This function is supposed to be called from `nntp-server-opened-hook'.
 It will make innd servers spawn an nnrpd process to allow actual article
 reading."
-  (nntp-send-command "^.*\r$" "MODE READER"))
+  (nntp-send-command "^.*\r?\n" "MODE READER"))
+
+(defun nntp-send-nosy-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"
+                    (read-string "NNTP user name: "))
+  (nntp-send-command "^.*\r?\n" "AUTHINFO PASS" 
+                    (read-string "NNTP password: ")))
 
 (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$" "AUTHINFO USER" (user-login-name))
-  (nntp-send-command "^.*\r$" "AUTHINFO PASS" (read-string "NNTP password: ")))
+  (nntp-send-command "^.*\r?\n" "AUTHINFO USER" (user-login-name))
+  (nntp-send-command "^.*\r?\n" "AUTHINFO PASS" 
+                    (read-string "NNTP password: ")))
+
+(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."
+  (when (file-exists-p "~/.nntp-authinfo")
+    (save-excursion
+      (set-buffer (get-buffer-create " *authinfo*"))
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      (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)))))
 
 (defun nntp-default-sentinel (proc status)
   "Default sentinel function for NNTP server process."
-  (let ((servers nntp-server-alist))
+  (let ((servers nntp-server-alist)
+       server)
     ;; Go through the alist of server names and find the name of the
     ;; server that the process that sent the signal is connected to.
     ;; If you get my drift.
-    (while (and servers 
-               (not (equal proc (nth 1 (assq 'nntp-server-process
-                                             (car servers))))))
-      (setq servers (cdr servers)))
-    (message "nntp: Connection closed to server %s." 
-            (or (car (car servers)) "(none)"))
-    (ding)))
+    (if (equal proc nntp-server-process)
+       (setq server nntp-address)
+      (while (and servers 
+                 (not (equal proc (nth 1 (assq 'nntp-server-process
+                                               (car servers))))))
+       (setq servers (cdr servers)))
+      (setq server (car (car servers))))
+    (when (and server
+              nntp-warn-about-losing-connection)
+      (message "nntp: Connection closed to server %s" server)
+      (ding))))
 
 (defun nntp-kill-connection (server)
+  "Choke the connection to SERVER."
   (let ((proc (nth 1 (assq 'nntp-server-process 
                           (assoc server nntp-server-alist)))))
-    (and proc (delete-process (process-name proc)))
+    (when proc 
+      (delete-process (process-name proc)))
     (nntp-close-server server)
     (setq nntp-status-string 
          (message "Connection timed out to server %s." server))
-    (ding)))
+    (ding)
+    (sit-for 1)))
 
 ;; Encoding and decoding of NNTP text.
 
@@ -644,19 +692,15 @@ It will prompt for a password."
     (goto-char (point-max))
     (or (bolp) (insert "\n"))
     ;; Delete status line.
-    (goto-char (point-min))
-    (delete-region (point) (progn (forward-line 1) (point)))
-    ;; Delete `^M' at the end of lines.
-    (while (not (eobp))
-      (end-of-line)
-      (and (= (preceding-char) ?\r)
-          (delete-char -1))
-      (forward-line 1))
+    (delete-region (goto-char (point-min)) (progn (forward-line 1) (point)))
+    ;; Delete `^M's.
+    (while (search-forward "\r" nil t)
+      (replace-match "" t t))
     ;; Delete `.' at end of the buffer (end of text mark).
     (goto-char (point-max))
     (forward-line -1)
-    (if (looking-at "^\\.$")
-       (delete-region (point) (progn (forward-line 1) (point))))
+    (when (looking-at "^\\.\n")
+      (delete-region (point) (progn (forward-line 1) (point))))
     ;; Replace `..' at beginning of line with `.'.
     (goto-char (point-min))
     ;; (replace-regexp "^\\.\\." ".")
@@ -668,21 +712,19 @@ It will prompt for a password."
 1. Insert `.' at beginning of line.
 2. Insert `.' at end of buffer (end of text mark)."
   (save-excursion
-    ;; Insert newline at end of buffer.
-    (goto-char (point-max))
-    (or (bolp) (insert "\n"))
     ;; Replace `.' at beginning of line with `..'.
     (goto-char (point-min))
-    ;; (replace-regexp "^\\." "..")
     (while (search-forward "\n." nil t)
       (insert "."))
-    ;; Insert `.' at end of buffer (end of text mark).
     (goto-char (point-max))
+    ;; Insert newline at end of buffer.
+    (or (bolp) (insert "\n"))
+    ;; Insert `.' at end of buffer (end of text mark).
     (insert ".\r\n")))
 
 \f
 ;;;
-;;; Synchronous Communication with NNTP Server.
+;;; Synchronous Communication with NNTP servers.
 ;;;
 
 (defun nntp-send-command (response cmd &rest args)
@@ -696,7 +738,7 @@ It will prompt for a password."
        (nntp-wait-for-response response)
       t)))
 
-(defun nntp-wait-for-response (regexp)
+(defun nntp-wait-for-response (regexp &optional slow)
   "Wait for server response which matches REGEXP."
   (save-excursion
     (let ((status t)
@@ -716,119 +758,258 @@ It will prompt for a password."
       (nntp-accept-response)
       (while wait
        (goto-char (point-min))
-       (cond ((looking-at "[23]")
-              (setq wait nil))
-             ((looking-at "[45]")
-              (setq status nil)
-              (setq wait nil))
-             (t (nntp-accept-response))))
+       (if slow
+           (progn
+             (cond ((re-search-forward "^[23][0-9][0-9]" nil t)
+                    (setq wait nil))
+                   ((re-search-forward "^[45][0-9][0-9]" nil t)
+                    (setq status nil)
+                    (setq wait nil))
+                   (t (nntp-accept-response)))
+             (if (not wait) (delete-region (point-min) 
+                                           (progn (beginning-of-line)
+                                                  (point)))))
+         (cond ((looking-at "[23]")
+                (setq wait nil))
+               ((looking-at "[45]")
+                (setq status nil)
+                (setq wait nil))
+               (t (nntp-accept-response)))))
       ;; Save status message.
       (end-of-line)
       (setq nntp-status-string
            (buffer-substring (point-min) (point)))
-      (if status
-         (progn
-           (setq wait t)
-           (while wait
-             (goto-char (point-max))
-             (forward-line -1)         ;(beginning-of-line)
-             ;;(message (buffer-substring
-             ;;         (point)
-             ;;         (save-excursion (end-of-line) (point))))
-             (if (looking-at regexp)
-                 (setq wait nil)
-               (if nntp-debug-read
-                   (let ((newnum (/ (buffer-size) dotsize)))
-                     (if (not (= dotnum newnum))
-                         (progn
-                           (setq dotnum newnum)
-                           (message "NNTP: Reading %s"
-                                    (make-string dotnum ?.))))))
-               (nntp-accept-response)))
-           ;; Remove "...".
-           (if (and nntp-debug-read (> dotnum 0))
-               (message ""))
-           ;; Successfully received server response.
-           t)))))
+      (when status
+       (setq wait t)
+       (while wait
+         (goto-char (point-max))
+         (forward-line -1)
+         (if (looking-at regexp)
+             (setq wait nil)
+           (when nntp-debug-read
+             (let ((newnum (/ (buffer-size) dotsize)))
+               (if (not (= dotnum newnum))
+                   (progn
+                     (setq dotnum newnum)
+                     (message "NNTP: Reading %s"
+                              (make-string dotnum ?.))))))
+           (nntp-accept-response)))
+       ;; Remove "...".
+       (when (and nntp-debug-read (> dotnum 0))
+         (message ""))
+       ;; Successfully received server response.
+       t))))
 
 \f
+
 ;;;
 ;;; Low-Level Interface to NNTP Server.
 ;;; 
 
-(defun nntp-retrieve-headers-with-xover (sequence)
-  (if (not nntp-server-xover)
-      ()
-    (let ((range (format "%d-%d" (car sequence)
-                        (nntp-last-element sequence))))
-      (prog1
-         (if (stringp nntp-server-xover)
-             (nntp-send-command "^\\.\r$" nntp-server-xover range)
-           (let ((commands nntp-xover-commands))
-             (while (and commands
-                         (eq t nntp-server-xover))
-               (nntp-send-command "^\\.\r$" (car commands) range)
-               (save-excursion
-                 (set-buffer nntp-server-buffer)
-                 (goto-char 1)
-                 (if (looking-at "[23]") 
-                     (setq nntp-server-xover (car commands))))
-               (setq commands (cdr commands)))
-             (if (eq t nntp-server-xover)
-                 (setq nntp-server-xover nil))
-             nntp-server-xover))
-       (if nntp-server-xover (nntp-decode-text) (erase-buffer))))))
+(defun nntp-find-group-and-number ()
+  (save-excursion
+    (save-restriction
+      (set-buffer nntp-server-buffer)
+      (narrow-to-region (goto-char (point-min))
+                       (or (search-forward "\n\n" nil t) (point-max)))
+      (goto-char (point-min))
+      ;; We first find the number by looking at the status line.
+      (let ((number (and (looking-at "2[0-9][0-9] +\\([0-9]+\\) ")
+                        (string-to-int
+                         (buffer-substring (match-beginning 1)
+                                           (match-end 1)))))
+           group newsgroups xref)
+       (and number (zerop number) (setq number nil))
+       ;; Then we find the group name.
+       (setq group
+             (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.
+              ((and (setq newsgroups (mail-fetch-field "newsgroups"))
+                    (not (string-match "," newsgroups)))
+               newsgroups)
+              ;; If there is more than one group in the Newsgroups
+              ;; header, then the Xref header should be filled out.
+              ;; We hazard a guess that the group that has this
+              ;; 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. 
+              ((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) 
+         (setq group (substring group 0 (match-beginning 0))))
+       (cons group number)))))
+
+(defun nntp-retrieve-headers-with-xover (articles &optional fetch-old)
+  (erase-buffer)
+  (cond 
+
+   ;; This server does not talk NOV.
+   ((not nntp-server-xover)
+    nil)
+
+   ;; We don't care about gaps.
+   ((or (not nntp-nov-gap)
+       fetch-old)
+    (nntp-send-xover-command 
+     (if fetch-old
+        (if (numberp fetch-old) 
+            (max 1 (- (car articles) fetch-old)) 
+          1)
+       (car articles))
+     (nntp-last-element articles) 'wait)
 
-(defun nntp-send-strings-to-server (&rest strings)
-  "Send list of STRINGS to news server as command and its arguments."
-  (let ((cmd (car strings))
-       (strings (cdr strings)))
-    ;; Command and each argument must be separated by one or more spaces.
-    (while strings
-      (setq cmd (concat cmd " " (car strings)))
-      (setq strings (cdr strings)))
-    ;; Command line must be terminated by a CR-LF.
-    (if (not (nntp-server-opened nntp-current-server))
-       (progn
-         (nntp-close-server nntp-address)
-         (if (not (nntp-open-server nntp-address))
-             (error (nntp-status-message)))
+    (goto-char (point-min))
+    (when (looking-at "[1-5][0-9][0-9] ")
+      (delete-region (point) (progn (forward-line 1) (point))))
+    (while (search-forward "\r" nil t)
+      (replace-match "" t t))
+    (goto-char (point-max))
+    (forward-line -1)
+    (when (looking-at "\\.")
+      (delete-region (point) (progn (forward-line 1) (point)))))
+
+   ;; We do it the hard way.  For each gap, an XOVER command is sent
+   ;; to the server.  We do not wait for a reply from the server, we
+   ;; just send them off as fast as we can.  That means that we have
+   ;; to count the number of responses we get back to find out when we
+   ;; have gotten all we asked for.
+   ((numberp nntp-nov-gap)
+    (let ((count 0)
+         (received 0)
+         (last-point (point-min))
+         (buf (current-buffer))
+         first)
+      ;; We have to check `nntp-server-xover'.  If it gets set to nil,
+      ;; that means that the server does not understand XOVER, but we
+      ;; won't know that until we try.
+      (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) 
+                   (< (- (nth 1 articles) (car articles)) nntp-nov-gap))
+         (setq articles (cdr articles)))
+
+       (when (nntp-send-xover-command first (car articles))
+         (setq articles (cdr articles)
+               count (1+ 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)))
+           (accept-process-output)
+           ;; On some Emacs versions the preceding function has
+           ;; a tendency to change the buffer. Perhaps. It's
+           ;; quite difficult to reporduce, because it only
+           ;; seems to happen once in a blue moon. 
+           (set-buffer buf) 
+           (while (progn
+                    (goto-char last-point)
+                    ;; Count replies.
+                    (while (re-search-forward "^[0-9][0-9][0-9] " nil t)
+                      (setq received (1+ received)))
+                    (setq last-point (point))
+                    (< received count))
+             (accept-process-output)
+             (set-buffer buf)))))
+
+      (when nntp-server-xover
+       ;; Wait for the reply from the final command.
+       (goto-char (point-max))
+       (re-search-backward "^[0-9][0-9][0-9] " nil t)
+       (when (looking-at "^[23]")
+         (while (progn
+                  (goto-char (point-max))
+                  (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)
+         (delete-char -1))
+       (goto-char (point-min))
+       (delete-matching-lines "^\\.$\\|^[1-5][0-9][0-9] ")))))
+
+  nntp-server-xover)
+
+(defun nntp-send-xover-command (beg end &optional wait-for-reply)
+  "Send the XOVER command to the server."
+  (let ((range (format "%d-%d" beg end)))
+    (if (stringp nntp-server-xover)
+       ;; If `nntp-server-xover' is a string, then we just send this
+       ;; command.
+       (if wait-for-reply
+           (nntp-send-command "^\\.\r?\n" nntp-server-xover range)
+         ;; We do not wait for the reply.
+         (nntp-send-strings-to-server 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. 
+       (while (and commands (eq nntp-server-xover 'try))
+         (nntp-send-command "^\\.\r?\n" (car commands) range)
+         (save-excursion
+           (set-buffer nntp-server-buffer)
+           (goto-char (point-min))
+           (and (looking-at "[23]") ; No error message.
+                ;; We also have to look at the lines.  Some buggy
+                ;; servers give back simple lines with just the
+                ;; article number.  How... helpful.
+                (progn
+                  (forward-line 1)
+                  (looking-at "[0-9]+\t...")) ; More text after number.
+                (setq nntp-server-xover (car commands))))
+         (setq commands (cdr commands)))
+       ;; If none of the commands worked, we disable XOVER.
+       (when (eq nntp-server-xover 'try)
          (save-excursion
            (set-buffer nntp-server-buffer)
-           (erase-buffer))))
-    (process-send-string nntp-server-process (concat cmd "\r\n"))))
+           (erase-buffer)
+           (setq nntp-server-xover nil)))
+       nntp-server-xover))))
+
+(defun nntp-send-strings-to-server (&rest strings)
+  "Send STRINGS to the server."
+  (let ((cmd (concat (mapconcat 'identity strings " ") "\r\n")))
+    ;; We open the nntp server if it is down.
+    (or (nntp-server-opened nntp-current-server)
+       (nntp-open-server nntp-current-server)
+       (error (nntp-status-message)))
+    ;; Send the strings.
+    (process-send-string nntp-server-process cmd)
+    t))
 
 (defun nntp-send-region-to-server (begin end)
-  "Send current buffer region (from BEGIN to END) to news server."
+  "Send the current buffer region (from BEGIN to END) to the server."
   (save-excursion
-    ;; We have to work in the buffer associated with NNTP server
-    ;;  process because of NEmacs hack.
-    (copy-to-buffer nntp-server-buffer begin end)
-    (set-buffer nntp-server-buffer)
-    (setq begin (point-min))
-    (setq end (point-max))
-    ;; `process-send-region' does not work if text to be sent is very
-    ;;  large. I don't know maximum size of text sent correctly.
-    (let ((last nil)
+    ;; If we're not the the nntp server buffer, we copy the region
+    ;; over to that buffer.  
+    (if (eq (get-buffer nntp-server-buffer) (current-buffer))
+       (let ((orig (current-buffer)))
+         (set-buffer nntp-server-buffer)
+         (erase-buffer)
+         (insert-buffer-substring orig begin end))
+      ;; We are in the nntp buffer, so we just narrow it.
+      (narrow-to-region begin end))
+    ;; `process-send-region' does not work if the text to be sent is very
+    ;; large, so we send it piecemeal.
+    (let ((last (point-min))
          (size 100))                   ;Size of text sent at once.
-      (save-restriction
-       (narrow-to-region begin end)
-       (goto-char begin)
-       (while (not (eobp))
-         ;;(setq last (min end (+ (point) size)))
-         ;; NEmacs gets confused if character at `last' is Kanji.
-         (setq last (save-excursion
-                      (goto-char (min end (+ (point) size)))
-                      (or (eobp) (forward-char 1)) ;Adjust point
-                      (point)))
-         (process-send-region nntp-server-process (point) last)
-         ;; I don't know whether the next codes solve the known
-         ;;  problem of communication error of GNU Emacs.
-         (accept-process-output)
-         ;;(sit-for 0)
-         (goto-char last))))
-    ;; We cannot erase buffer, because reply may be received.
-    (delete-region begin end)))
+      (while (/= last (point-max))
+       (process-send-region 
+        nntp-server-process last (setq last (min (+ last size) (point-max))))
+       ;; Read any output from the server.  May be unnecessary.
+       (accept-process-output)))
+    ;; Delete the area we sent.
+    (delete-region (point-min) (point-max))
+    (widen)))
 
 (defun nntp-open-server-semi-internal (server &optional service)
   "Open SERVER.
@@ -838,32 +1019,47 @@ If SERVICE, this this as the port number."
        (status nil)
        (timer 
         (and nntp-connection-timeout 
-             (run-at-time nntp-connection-timeout
-                          nil 'nntp-kill-connection server))))
-    (setq nntp-status-string "")
-    (message "nntp: Connecting to server on %s..." server)
-    (cond ((and server (nntp-open-server-internal server service))
-          (setq nntp-address server)
-          (setq status
-                (condition-case nil
-                    (nntp-wait-for-response "^[23].*\r$")
-                  (error nil)
-                  (quit nil)))
-          (or status (nntp-close-server-internal server))
-          (and nntp-server-process
-               (progn
-                 (set-process-sentinel 
-                  nntp-server-process 'nntp-default-sentinel)
-                 ;; You can send commands at startup like AUTHINFO here.
-                 ;; Added by Hallvard B Furuseth <h.b.furuseth@usit.uio.no>
-                 (run-hooks 'nntp-server-opened-hook))))
-         ((null server)
-          (setq nntp-status-string "NNTP server is not specified.")))
-    (and timer (cancel-timer timer))
-    (message "")
-    (or status
-       (setq nntp-current-server nil))
-    status))
+             (cond
+              ((fboundp 'run-at-time)
+               (run-at-time nntp-connection-timeout
+                            nil 'nntp-kill-connection server))
+              ((fboundp 'start-itimer)
+               ;; Not sure if this will work or not, only one way to
+               ;; find out
+               (eval '(start-itimer "nntp-timeout"
+                                    (lambda ()
+                                      (nntp-kill-connection server))
+                                    nntp-connection-timeout nil)))))))
+    (save-excursion
+      (set-buffer nntp-server-buffer)
+      (setq nntp-status-string "")
+      (message "nntp: Connecting to server on %s..." server)
+      (cond ((and server (nntp-open-server-internal server service))
+            (setq nntp-address server)
+            (setq status
+                  (condition-case nil
+                      (nntp-wait-for-response "^[23].*\r?\n" 'slow)
+                    (error nil)
+                    (quit nil)))
+            (or status (nntp-close-server-internal server))
+            (and nntp-server-process
+                 (progn
+                   (set-process-sentinel 
+                    nntp-server-process 'nntp-default-sentinel)
+                   ;; You can send commands at startup like AUTHINFO here.
+                   ;; Added by Hallvard B Furuseth <h.b.furuseth@usit.uio.no>
+                   (run-hooks 'nntp-server-opened-hook))))
+           ((null server)
+            (setq nntp-status-string "NNTP server is not specified."))
+           (t                          ; We couldn't open the server.
+            (setq nntp-status-string 
+                  (buffer-substring (point-min) (point-max)))))
+      (and timer (cancel-timer timer))
+      (message "")
+      (or status
+         (setq nntp-current-server nil
+               nntp-async-number nil))
+      status)))
 
 (defun nntp-open-server-internal (server &optional service)
   "Open connection to news server on SERVER by SERVICE (default is nntp)."
@@ -883,20 +1079,41 @@ If SERVICE, this this as the port number."
            (setq nntp-server-process proc)
            ;; Suggested by Hallvard B Furuseth <h.b.furuseth@usit.uio.no>.
            (process-kill-without-query proc)
-           (setq nntp-server-xover t)
-           (setq nntp-server-list-active-group t)
            (setq nntp-address server)
            ;; It is possible to change kanji-fileio-code in this hook.
            (run-hooks 'nntp-server-hook)
-           nntp-server-process)))))
+           (push proc nntp-opened-connections)
+           nntp-server-process)
+       (setq nntp-status-string (format "Couldn't open server %s" server))
+       nil))))
 
 (defun nntp-open-network-stream (server)
-  (open-network-stream "nntpd" nntp-server-buffer server nntp-port-number))
-
-(defun nntp-open-rlogin-stream (server)
-  (apply 'start-process "nntpd" nntp-server-buffer "rsh" 
-        "-l" (or nntp-rlogin-user-name (user-login-name))
-        server nntp-rlogin-parameters))
+  (open-network-stream 
+   "nntpd" nntp-server-buffer server nntp-port-number))
+
+(defun nntp-open-rlogin (server)
+  (let ((proc (start-process "nntpd" nntp-server-buffer "rsh" server)))
+    (process-send-string proc (mapconcat 'identity nntp-rlogin-parameters
+                                        " "))
+    (process-send-string proc "\n")))
+
+(defun nntp-telnet-to-machine ()
+  (let (b)
+    (telnet "localhost")
+    (goto-char (point-min))
+    (while (not (re-search-forward "^login: *" nil t))
+      (sit-for 1)
+      (goto-char (point-min)))
+    (goto-char (point-max))
+    (insert "larsi")
+    (telnet-send-input)
+    (setq b (point))
+    (while (not (re-search-forward ">" nil t))
+      (sit-for 1)
+      (goto-char b))
+    (goto-char (point-max))
+    (insert "ls")
+    (telnet-send-input)))
 
 (defun nntp-close-server-internal (&optional server)
   "Close connection to news server."
@@ -914,25 +1131,29 @@ defining this function as macro."
   ;;  accept-process-output is called.
   ;; Suggested by Jason Venner <jason@violet.berkeley.edu>.
   ;; This is a copy of `nntp-default-sentinel'.
-  (if (or (not nntp-server-process)
-         (not (memq (process-status nntp-server-process) '(open run))))
-      (error "nntp: Process connection closed; %s" (nntp-status-message))
-    (if nntp-buggy-select
-       (progn
-         ;; We cannot use `accept-process-output'.
-         ;; Fujitsu UTS requires messages during sleep-for. I don't know why.
-         (message "NNTP: Reading...")
-         (sleep-for 1)
-         (message ""))
-      (condition-case errorcode
-         (accept-process-output nntp-server-process)
-       (error
-        (cond ((string-equal "select error: Invalid argument" 
-                             (nth 1 errorcode))
-               ;; Ignore select error.
-               nil)
-              (t
-               (signal (car errorcode) (cdr errorcode)))))))))
+  (let ((buf (current-buffer)))
+    (prog1
+       (if (or (not nntp-server-process)
+               (not (memq (process-status nntp-server-process) '(open run))))
+           (error "nntp: Process connection closed; %s" (nntp-status-message))
+         (if nntp-buggy-select
+             (progn
+               ;; We cannot use `accept-process-output'.
+               ;; Fujitsu UTS requires messages during sleep-for.
+               ;; I don't know why.
+               (message "NNTP: Reading...")
+               (sleep-for 1)
+               (message ""))
+           (condition-case errorcode
+               (accept-process-output nntp-server-process)
+             (error
+              (cond ((string-equal "select error: Invalid argument" 
+                                   (nth 1 errorcode))
+                     ;; Ignore select error.
+                     nil)
+                    (t
+                     (signal (car errorcode) (cdr errorcode))))))))
+      (set-buffer buf))))
 
 (defun nntp-last-element (list)
   "Return last element of LIST."
@@ -940,16 +1161,17 @@ defining this function as macro."
     (setq list (cdr list)))
   (car list))
 
-(defun nntp-possibly-change-server (newsgroup server)
-  (let ((result t))
-    ;; We see whether it is necessary to change newsgroup.
-    (and newsgroup 
-        (or (not (string= newsgroup nntp-current-group)))
-        (progn
-          (setq result (nntp-request-group newsgroup server))
-          (setq nntp-current-group newsgroup)))
-    result))
-
+(defun nntp-possibly-change-server (newsgroup server &optional connectionless)
+  "Check whether the virtual server needs changing."
+  (if (and server
+          (not (nntp-server-opened server)))
+      ;; This virtual server isn't open, so we (re)open it here.
+      (nntp-open-server server nil t))
+  (if (and newsgroup 
+          (not (equal newsgroup nntp-current-group)))
+      ;; Set the proper current group.
+      (nntp-request-group newsgroup server)))
 (defun nntp-try-list-active (group)
   (nntp-list-active-group group)
   (save-excursion
@@ -960,6 +1182,66 @@ defining this function as macro."
          (t
           (setq nntp-server-list-active-group t)))))
 
+(defun nntp-async-server-opened ()
+  (and nntp-async-process
+       (memq (process-status nntp-async-process) '(open run))))
+
+(defun nntp-async-open-server ()
+  (save-excursion
+    (set-buffer (generate-new-buffer " *async-nntp*"))
+    (setq nntp-async-buffer (current-buffer))
+    (buffer-disable-undo (current-buffer)))
+  (let ((nntp-server-process nil)
+       (nntp-server-buffer nntp-async-buffer))
+    (nntp-open-server-semi-internal nntp-address nntp-port-number)
+    (if (not (setq nntp-async-process nntp-server-process))
+       (progn
+         (setq nntp-async-number nil))
+      (set-process-buffer nntp-async-process nntp-async-buffer))))
+
+(defun nntp-async-fetch-articles (article)
+  (if (stringp article)
+      ()
+    (let ((articles (cdr (memq (assq article nntp-async-articles)
+                              nntp-async-articles)))
+         (max (cond ((numberp nntp-async-number)
+                     nntp-async-number) 
+                    ((eq nntp-async-number t)
+                     (length nntp-async-articles))
+                    (t 0)))
+         nart)
+      (while (and (>= (setq max (1- max)) 0)
+                 articles)
+       (or (memq (setq nart (car (car articles))) nntp-async-fetched)
+           (progn
+             (nntp-async-send-strings "ARTICLE " (int-to-string nart))
+             (setq nntp-async-fetched (cons nart nntp-async-fetched))))
+       (setq articles (cdr articles))))))
+
+(defun nntp-async-send-strings (&rest strings)
+  (let ((cmd (concat (mapconcat 'identity strings " ") "\r\n")))
+    (or (nntp-async-server-opened)
+       (nntp-async-open-server)
+       (error (nntp-status-message)))
+    (process-send-string nntp-async-process cmd)))
+
+(defun nntp-async-request-group (group)
+  (if (equal group nntp-current-group)
+      ()
+    (let ((asyncs (assoc group nntp-async-group-alist)))
+      ;; A new group has been selected, so we push the current state
+      ;; of async articles on an alist, and pull the old state off.
+      (setq nntp-async-group-alist 
+           (cons (list nntp-current-group
+                       nntp-async-articles nntp-async-fetched
+                       nntp-async-process)
+                 (delq asyncs nntp-async-group-alist)))
+      (and asyncs
+          (progn
+            (setq nntp-async-articles (nth 1 asyncs))
+            (setq nntp-async-fetched (nth 2 asyncs))
+            (setq nntp-async-process (nth 3 asyncs)))))))
+
 (provide 'nntp)
 
 ;;; nntp.el ends here