* imap.el (imap-ssl-open): Don't depend on ssl.el.
[gnus] / lisp / nntp.el
1 ;;; nntp.el --- nntp access for Gnus
2
3 ;; Copyright (C) 1987, 1988, 1989, 1990, 1992, 1993, 1994, 1995, 1996,
4 ;; 1997, 1998, 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
5
6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;; Keywords: news
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published
13 ;; by the Free Software Foundation; either version 2, or (at your
14 ;; option) any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful, but
17 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 ;; General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
24
25 ;;; Commentary:
26
27 ;;; Code:
28
29 (require 'nnheader)
30 (require 'nnoo)
31 (require 'gnus-util)
32
33 (nnoo-declare nntp)
34
35 (eval-when-compile (require 'cl))
36
37 (defvoo nntp-address nil
38   "Address of the physical nntp server.")
39
40 (defvoo nntp-port-number "nntp"
41   "Port number on the physical nntp server.")
42
43 (defvoo nntp-server-opened-hook '(nntp-send-mode-reader)
44   "*Hook used for sending commands to the server at startup.
45 The default value is `nntp-send-mode-reader', which makes an innd
46 server spawn an nnrpd server.")
47
48 (defvoo nntp-authinfo-function 'nntp-send-authinfo
49   "Function used to send AUTHINFO to the server.
50 It is called with no parameters.")
51
52 (defvoo nntp-server-action-alist
53     '(("nntpd 1\\.5\\.11t"
54        (remove-hook 'nntp-server-opened-hook 'nntp-send-mode-reader))
55       ("NNRP server Netscape"
56        (setq nntp-server-list-active-group nil)))
57   "Alist of regexps to match on server types and actions to be taken.
58 For instance, if you want Gnus to beep every time you connect
59 to innd, you could say something like:
60
61 \(setq nntp-server-action-alist
62        '((\"innd\" (ding))))
63
64 You probably don't want to do that, though.")
65
66 (defvoo nntp-open-connection-function 'nntp-open-network-stream
67   "*Function used for connecting to a remote system.
68 It will be called with the buffer to output in as argument.
69
70 Currently, five such functions are provided (please refer to their
71 respective doc string for more information), three of them establishing
72 direct connections to the nntp server, and two of them using an indirect
73 host.
74
75 Direct connections:
76 - `nntp-open-network-stream' (the default),
77 - `nntp-open-ssl-stream',
78 - `nntp-open-telnet-stream'.
79
80 Indirect connections:
81 - `nntp-open-via-rlogin-and-telnet',
82 - `nntp-open-via-telnet-and-telnet'.")
83
84 (defvoo nntp-pre-command nil
85   "*Pre-command to use with the various nntp-open-via-* methods.
86 This is where you would put \"runsocks\" or stuff like that.")
87
88 (defvoo nntp-telnet-command "telnet"
89   "*Telnet command used to connect to the nntp server.
90 This command is used by the various nntp-open-via-* methods.")
91
92 (defvoo nntp-telnet-switches '("-8")
93   "*Switches given to the telnet command `nntp-telnet-command'.")
94
95 (defvoo nntp-end-of-line "\r\n"
96   "*String to use on the end of lines when talking to the NNTP server.
97 This is \"\\r\\n\" by default, but should be \"\\n\" when
98 using and indirect connection method (nntp-open-via-*).")
99
100 (defvoo nntp-via-rlogin-command "rsh"
101   "*Rlogin command used to connect to an intermediate host.
102 This command is used by the `nntp-open-via-rlogin-and-telnet' method.
103 The default is \"rsh\", but \"ssh\" is a popular alternative.")
104
105 (defvoo nntp-via-rlogin-command-switches nil
106   "*Switches given to the rlogin command `nntp-via-rlogin-command'.
107 If you use \"ssh\" for `nntp-via-rlogin-command', you may set this to
108 \(\"-C\") in order to compress all data connections, otherwise set this
109 to \(\"-t\") or (\"-C\" \"-t\") if the telnet command requires a pseudo-tty
110 allocation on an intermediate host.")
111
112 (defvoo nntp-via-telnet-command "telnet"
113   "*Telnet command used to connect to an intermediate host.
114 This command is used by the `nntp-open-via-telnet-and-telnet' method.")
115
116 (defvoo nntp-via-telnet-switches '("-8")
117   "*Switches given to the telnet command `nntp-via-telnet-command'.")
118
119 (defvoo nntp-via-user-name nil
120   "*User name to log in on an intermediate host with.
121 This variable is used by the `nntp-open-via-telnet-and-telnet' method.")
122
123 (defvoo nntp-via-user-password nil
124   "*Password to use to log in on an intermediate host with.
125 This variable is used by the `nntp-open-via-telnet-and-telnet' method.")
126
127 (defvoo nntp-via-address nil
128   "*Address of an intermediate host to connect to.
129 This variable is used by the `nntp-open-via-rlogin-and-telnet' and
130 `nntp-open-via-telnet-and-telnet' methods.")
131
132 (defvoo nntp-via-envuser nil
133   "*Whether both telnet client and server support the ENVIRON option.
134 If non-nil, there will be no prompt for a login name.")
135
136 (defvoo nntp-via-shell-prompt "bash\\|\$ *\r?$\\|> *\r?"
137   "*Regular expression to match the shell prompt on an intermediate host.
138 This variable is used by the `nntp-open-via-telnet-and-telnet' method.")
139
140 (defvoo nntp-large-newsgroup 50
141   "*The number of the articles which indicates a large newsgroup.
142 If the number of the articles is greater than the value, verbose
143 messages will be shown to indicate the current status.")
144
145 (defvoo nntp-maximum-request 400
146   "*The maximum number of the requests sent to the NNTP server at one time.
147 If Emacs hangs up while retrieving headers, set the variable to a
148 lower value.")
149
150 (defvoo nntp-nov-is-evil nil
151   "*If non-nil, nntp will never attempt to use XOVER when talking to the server.")
152
153 (defvoo nntp-xover-commands '("XOVER" "XOVERVIEW")
154   "*List of strings that are used as commands to fetch NOV lines from a server.
155 The strings are tried in turn until a positive response is gotten.  If
156 none of the commands are successful, nntp will just grab headers one
157 by one.")
158
159 (defvoo nntp-nov-gap 5
160   "*Maximum allowed gap between two articles.
161 If the gap between two consecutive articles is bigger than this
162 variable, split the XOVER request into two requests.")
163
164 (defvoo nntp-prepare-server-hook nil
165   "*Hook run before a server is opened.
166 If can be used to set up a server remotely, for instance.  Say you
167 have an account at the machine \"other.machine\".  This machine has
168 access to an NNTP server that you can't access locally.  You could
169 then use this hook to rsh to the remote machine and start a proxy NNTP
170 server there that you can connect to.  See also
171 `nntp-open-connection-function'")
172
173 (defvoo nntp-warn-about-losing-connection t
174   "*If non-nil, beep when a server closes connection.")
175
176 (defvoo nntp-coding-system-for-read 'binary
177   "*Coding system to read from NNTP.")
178
179 (defvoo nntp-coding-system-for-write 'binary
180   "*Coding system to write to NNTP.")
181
182 (defcustom nntp-authinfo-file "~/.authinfo"
183   ".netrc-like file that holds nntp authinfo passwords."
184   :type
185   '(choice file
186            (repeat :tag "Entries"
187                    :menu-tag "Inline"
188                    (list :format "%v"
189                          :value ("" ("login" . "") ("password" . ""))
190                          (string :tag "Host")
191                          (checklist :inline t
192                                     (cons :format "%v"
193                                           (const :format "" "login")
194                                           (string :format "Login: %v"))
195                                     (cons :format "%v"
196                                           (const :format "" "password")
197                                           (string :format "Password: %v")))))))
198
199 \f
200
201 (defvoo nntp-connection-timeout nil
202   "*Number of seconds to wait before an nntp connection times out.
203 If this variable is nil, which is the default, no timers are set.
204 NOTE: This variable is never seen to work in Emacs 20 and XEmacs 21.")
205
206 (defvoo nntp-prepare-post-hook nil
207   "*Hook run just before posting an article.  It is supposed to be used
208 to insert Cancel-Lock headers.")
209
210 (defvoo nntp-read-timeout (if (string-match "windows-nt\\|os/2\\|emx\\|cygwin"
211                                             (symbol-name system-type))
212                               1.0
213                             0.1)
214   "How long nntp should wait between checking for the end of output.
215 Shorter values mean quicker response, but is more CPU intensive.")
216
217 ;;; Internal variables.
218
219 (defvar nntp-record-commands nil
220   "*If non-nil, nntp will record all commands in the \"*nntp-log*\" buffer.")
221
222 (defvar nntp-have-messaged nil)
223
224 (defvar nntp-process-wait-for nil)
225 (defvar nntp-process-to-buffer nil)
226 (defvar nntp-process-callback nil)
227 (defvar nntp-process-decode nil)
228 (defvar nntp-process-start-point nil)
229 (defvar nntp-inside-change-function nil)
230 (defvoo nntp-last-command-time nil)
231 (defvoo nntp-last-command nil)
232 (defvoo nntp-authinfo-password nil)
233 (defvoo nntp-authinfo-user nil)
234
235 (defvar nntp-connection-list nil)
236
237 (defvoo nntp-server-type nil)
238 (defvoo nntp-connection-alist nil)
239 (defvoo nntp-status-string "")
240 (defconst nntp-version "nntp 5.0")
241 (defvoo nntp-inhibit-erase nil)
242 (defvoo nntp-inhibit-output nil)
243
244 (defvoo nntp-server-xover 'try)
245 (defvoo nntp-server-list-active-group 'try)
246
247 (defvar nntp-async-needs-kluge
248   (string-match "^GNU Emacs 20\\.3\\." (emacs-version))
249   "*When non-nil, nntp will poll asynchronous connections
250 once a second.  By default, this is turned on only for Emacs
251 20.3, which has a bug that breaks nntp's normal method of
252 noticing asynchronous data.")
253
254 (defvar nntp-async-timer nil)
255 (defvar nntp-async-process-list nil)
256
257 (defvar nntp-ssl-program 
258   "openssl s_client -quiet -ssl3 -connect %s:%p"
259 "A string containing commands for SSL connections.
260 Within a string, %s is replaced with the server address and %p with
261 port number on server.  The program should accept IMAP commands on
262 stdin and return responses to stdout.")
263
264 (eval-and-compile
265   (autoload 'mail-source-read-passwd "mail-source"))
266
267 \f
268
269 ;;; Internal functions.
270
271 (defsubst nntp-send-string (process string)
272   "Send STRING to PROCESS."
273   ;; We need to store the time to provide timeouts, and
274   ;; to store the command so the we can replay the command
275   ;; if the server gives us an AUTHINFO challenge.
276   (setq nntp-last-command-time (current-time)
277         nntp-last-command string)
278   (when nntp-record-commands
279     (nntp-record-command string))
280   (process-send-string process (concat string nntp-end-of-line))
281   (or (memq (process-status process) '(open run))
282       (nntp-report "Server closed connection")))
283
284 (defun nntp-record-command (string)
285   "Record the command STRING."
286   (save-excursion
287     (set-buffer (get-buffer-create "*nntp-log*"))
288     (goto-char (point-max))
289     (let ((time (current-time)))
290       (insert (format-time-string "%Y%m%dT%H%M%S" time)
291               "." (format "%03d" (/ (nth 2 time) 1000))
292               " " nntp-address " " string "\n"))))
293
294 (defun nntp-report (&rest args)
295   "Report an error from the nntp backend.  The first string in ARGS
296 can be a format string.  For some commands, the failed command may be
297 retried once before actually displaying the error report."
298
299   (when nntp-record-commands
300     (nntp-record-command "*** CALLED nntp-report ***"))
301
302   (nnheader-report 'nntp args)
303
304   (apply 'error args))
305
306 (defun nntp-report-1 (&rest args)
307   "Throws out to nntp-with-open-group-error so that the connection may
308 be restored and the command retried."
309
310   (when nntp-record-commands
311     (nntp-record-command "*** CONNECTION LOST ***"))
312
313   (throw 'nntp-with-open-group-error t))
314
315 (defsubst nntp-wait-for (process wait-for buffer &optional decode discard)
316   "Wait for WAIT-FOR to arrive from PROCESS."
317   (save-excursion
318     (set-buffer (process-buffer process))
319     (goto-char (point-min))
320     (while (and (or (not (memq (char-after (point)) '(?2 ?3 ?4 ?5)))
321                     (looking-at "480"))
322                 (memq (process-status process) '(open run)))
323       (when (looking-at "480")
324         (nntp-handle-authinfo process))
325       (when (looking-at "^.*\n")
326         (delete-region (point) (progn (forward-line 1) (point))))
327       (nntp-accept-process-output process)
328       (goto-char (point-min)))
329     (prog1
330         (cond
331          ((looking-at "[45]")
332           (progn
333             (nntp-snarf-error-message)
334             nil))
335          ((not (memq (process-status process) '(open run)))
336           (nntp-report "Server closed connection"))
337          (t
338           (goto-char (point-max))
339           (let ((limit (point-min))
340                 response)
341             (while (not (re-search-backward wait-for limit t))
342               (nntp-accept-process-output process)
343               ;; We assume that whatever we wait for is less than 1000
344               ;; characters long.
345               (setq limit (max (- (point-max) 1000) (point-min)))
346               (goto-char (point-max)))
347             (setq response (match-string 0))
348             (with-current-buffer nntp-server-buffer
349               (setq nntp-process-response response)))
350           (nntp-decode-text (not decode))
351           (unless discard
352             (save-excursion
353               (set-buffer buffer)
354               (goto-char (point-max))
355               (insert-buffer-substring (process-buffer process))
356               ;; Nix out "nntp reading...." message.
357               (when nntp-have-messaged
358                 (setq nntp-have-messaged nil)
359                 (nnheader-message 5 ""))))
360           t))
361       (unless discard
362         (erase-buffer)))))
363
364 (defun nntp-kill-buffer (buffer)
365   (when (buffer-name buffer)
366     (kill-buffer buffer)
367     (nnheader-init-server-buffer)))
368
369 (defsubst nntp-find-connection (buffer)
370   "Find the connection delivering to BUFFER."
371   (let ((alist nntp-connection-alist)
372         (buffer (if (stringp buffer) (get-buffer buffer) buffer))
373         process entry)
374     (while (and alist (setq entry (pop alist)))
375       (when (eq buffer (cadr entry))
376         (setq process (car entry)
377               alist nil)))
378     (when process
379       (if (memq (process-status process) '(open run))
380           process
381         (nntp-kill-buffer (process-buffer process))
382         (setq nntp-connection-alist (delq entry nntp-connection-alist))
383         nil))))
384
385 (defsubst nntp-find-connection-entry (buffer)
386   "Return the entry for the connection to BUFFER."
387   (assq (nntp-find-connection buffer) nntp-connection-alist))
388
389 (defun nntp-find-connection-buffer (buffer)
390   "Return the process connection buffer tied to BUFFER."
391   (let ((process (nntp-find-connection buffer)))
392     (when process
393       (process-buffer process))))
394
395 (defsubst nntp-retrieve-data (command address port buffer
396                                       &optional wait-for callback decode)
397   "Use COMMAND to retrieve data into BUFFER from PORT on ADDRESS."
398   (let ((process (or (nntp-find-connection buffer)
399                      (nntp-open-connection buffer))))
400     (if process
401         (progn
402           (unless (or nntp-inhibit-erase nnheader-callback-function)
403             (save-excursion
404               (set-buffer (process-buffer process))
405               (erase-buffer)))
406           (condition-case err
407               (progn
408                 (when command
409                   (nntp-send-string process command))
410                 (cond
411                  ((eq callback 'ignore)
412                   t)
413                  ((and callback wait-for)
414                   (nntp-async-wait process wait-for buffer decode callback)
415                   t)
416                  (wait-for
417                   (nntp-wait-for process wait-for buffer decode))
418                  (t t)))
419             (error
420              (nnheader-report 'nntp "Couldn't open connection to %s: %s"
421                               address err))
422             (quit
423              (message "Quit retrieving data from nntp")
424              (signal 'quit nil)
425              nil)))
426       (nnheader-report 'nntp "Couldn't open connection to %s" address))))
427
428 (defsubst nntp-send-command (wait-for &rest strings)
429   "Send STRINGS to server and wait until WAIT-FOR returns."
430   (when (and (not nnheader-callback-function)
431              (not nntp-inhibit-output))
432     (save-excursion
433       (set-buffer nntp-server-buffer)
434       (erase-buffer)))
435   (let* ((command (mapconcat 'identity strings " "))
436          (process (nntp-find-connection nntp-server-buffer))
437          (buffer (and process (process-buffer process)))
438          (pos (and buffer (with-current-buffer buffer (point)))))
439     (if process
440         (prog1
441             (nntp-retrieve-data command
442                                 nntp-address nntp-port-number
443                                 nntp-server-buffer
444                                 wait-for nnheader-callback-function)
445           ;; If nothing to wait for, still remove possibly echo'ed commands.
446           ;; We don't have echos if nntp-open-connection-function
447           ;; is `nntp-open-network-stream', so we skip this in that case.
448           (unless (or wait-for
449                       (equal nntp-open-connection-function
450                              'nntp-open-network-stream))
451             (nntp-accept-response)
452             (save-excursion
453               (set-buffer buffer)
454               (goto-char pos)
455               (if (looking-at (regexp-quote command))
456                   (delete-region pos (progn (forward-line 1)
457                                             (gnus-point-at-bol))))
458               )))
459       (nnheader-report 'nntp "Couldn't open connection to %s."
460                        nntp-address))))
461
462 (defun nntp-send-command-nodelete (wait-for &rest strings)
463   "Send STRINGS to server and wait until WAIT-FOR returns."
464   (let* ((command (mapconcat 'identity strings " "))
465          (process (nntp-find-connection nntp-server-buffer))
466          (buffer (and process (process-buffer process)))
467          (pos (and buffer (with-current-buffer buffer (point)))))
468     (if process
469         (prog1
470             (nntp-retrieve-data command
471                                 nntp-address nntp-port-number
472                                 nntp-server-buffer
473                                 wait-for nnheader-callback-function)
474           ;; If nothing to wait for, still remove possibly echo'ed commands
475           (unless wait-for
476             (nntp-accept-response)
477             (save-excursion
478               (set-buffer buffer)
479               (goto-char pos)
480               (if (looking-at (regexp-quote command))
481                   (delete-region pos (progn (forward-line 1)
482                                             (gnus-point-at-bol))))
483               )))
484       (nnheader-report 'nntp "Couldn't open connection to %s."
485                        nntp-address))))
486
487 (defun nntp-send-command-and-decode (wait-for &rest strings)
488   "Send STRINGS to server and wait until WAIT-FOR returns."
489   (when (and (not nnheader-callback-function)
490              (not nntp-inhibit-output))
491     (save-excursion
492       (set-buffer nntp-server-buffer)
493       (erase-buffer)))
494   (let* ((command (mapconcat 'identity strings " "))
495          (process (nntp-find-connection nntp-server-buffer))
496          (buffer (and process (process-buffer process)))
497          (pos (and buffer (with-current-buffer buffer (point)))))
498     (if process
499         (prog1
500             (nntp-retrieve-data command
501                                 nntp-address nntp-port-number
502                                 nntp-server-buffer
503                                 wait-for nnheader-callback-function t)
504           ;; If nothing to wait for, still remove possibly echo'ed commands
505           (unless wait-for
506             (nntp-accept-response)
507             (save-excursion
508           (set-buffer buffer)
509           (goto-char pos)
510           (if (looking-at (regexp-quote command))
511               (delete-region pos (progn (forward-line 1) (gnus-point-at-bol))))
512           )))
513       (nnheader-report 'nntp "Couldn't open connection to %s."
514                        nntp-address))))
515
516
517 (defun nntp-send-buffer (wait-for)
518   "Send the current buffer to server and wait until WAIT-FOR returns."
519   (when (and (not nnheader-callback-function)
520              (not nntp-inhibit-output))
521     (save-excursion
522       (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
523       (erase-buffer)))
524   (nntp-encode-text)
525   (mm-with-unibyte-current-buffer
526     ;; Some encoded unicode text contains character 0x80-0x9f e.g. Euro.
527     (process-send-region (nntp-find-connection nntp-server-buffer)
528                          (point-min) (point-max)))
529   (nntp-retrieve-data
530    nil nntp-address nntp-port-number nntp-server-buffer
531    wait-for nnheader-callback-function))
532
533 \f
534
535 ;;; Interface functions.
536
537 (nnoo-define-basics nntp)
538
539 (defsubst nntp-next-result-arrived-p ()
540   (cond
541    ;; A result that starts with a 2xx code is terminated by
542    ;; a line with only a "." on it.
543    ((eq (char-after) ?2)
544     (if (re-search-forward "\n\\.\r?\n" nil t)
545         t
546       nil))
547    ;; A result that starts with a 3xx or 4xx code is terminated
548    ;; by a newline.
549    ((looking-at "[34]")
550     (if (search-forward "\n" nil t)
551         t
552       nil))
553    ;; No result here.
554    (t
555     nil)))
556
557 (eval-when-compile
558   (defvar nntp-with-open-group-internal nil)
559   (defvar nntp-report-n nil))
560
561 (defmacro nntp-with-open-group (group server &optional connectionless &rest forms)
562   "Protect against servers that don't like clients that keep idle connections opens.
563 The problem being that these servers may either close a connection or
564 simply ignore any further requests on a connection.  Closed
565 connections are not detected until accept-process-output has updated
566 the process-status.  Dropped connections are not detected until the
567 connection timeouts (which may be several minutes) or
568 nntp-connection-timeout has expired.  When these occur
569 nntp-with-open-group, opens a new connection then re-issues the NNTP
570 command whose response triggered the error."
571   (when (and (listp connectionless)
572              (not (eq connectionless nil)))
573     (setq forms (cons connectionless forms)
574           connectionless nil))
575   `(letf ((nntp-report-n (symbol-function 'nntp-report))
576           ((symbol-function 'nntp-report) (symbol-function 'nntp-report-1))
577           (nntp-with-open-group-internal nil))
578      (while (catch 'nntp-with-open-group-error
579               ;; Open the connection to the server
580               ;; NOTE: Existing connections are NOT tested.
581               (nntp-possibly-change-group ,group ,server ,connectionless)
582
583               (let ((timer
584                      (and nntp-connection-timeout
585                           (nnheader-run-at-time
586                            nntp-connection-timeout nil
587                            '(lambda ()
588                               (let ((process (nntp-find-connection
589                                               nntp-server-buffer))
590                                     (buffer  (and process
591                                                   (process-buffer process))))
592                                 ;; When I an able to identify the
593                                 ;; connection to the server AND I've
594                                 ;; received NO reponse for
595                                 ;; nntp-connection-timeout seconds.
596                                 (when (and buffer (eq 0 (buffer-size buffer)))
597                                   ;; Close the connection.  Take no
598                                   ;; other action as the accept input
599                                   ;; code will handle the closed
600                                   ;; connection.
601                                   (nntp-kill-buffer buffer))))))))
602                 (unwind-protect
603                     (setq nntp-with-open-group-internal
604                           (condition-case nil
605                               (progn ,@forms)
606                             (quit
607                              (nntp-close-server)
608                              (signal 'quit nil))))
609                   (when timer
610                     (nnheader-cancel-timer timer)))
611                 nil))
612        (setf (symbol-function 'nntp-report) nntp-report-n))
613      nntp-with-open-group-internal))
614
615 (deffoo nntp-retrieve-headers (articles &optional group server fetch-old)
616   "Retrieve the headers of ARTICLES."
617   (nntp-with-open-group
618    group server
619    (save-excursion
620      (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
621      (erase-buffer)
622      (if (and (not gnus-nov-is-evil)
623               (not nntp-nov-is-evil)
624               (nntp-retrieve-headers-with-xover articles fetch-old))
625          ;; We successfully retrieved the headers via XOVER.
626          'nov
627        ;; XOVER didn't work, so we do it the hard, slow and inefficient
628        ;; way.
629        (let ((number (length articles))
630              (articles articles)
631              (count 0)
632              (received 0)
633              (last-point (point-min))
634              (buf (nntp-find-connection-buffer nntp-server-buffer))
635              (nntp-inhibit-erase t)
636              article)
637          ;; Send HEAD commands.
638          (while (setq article (pop articles))
639            (nntp-send-command
640             nil
641             "HEAD" (if (numberp article)
642                        (int-to-string article)
643                      ;; `articles' is either a list of article numbers
644                      ;; or a list of article IDs.
645                      article))
646            (incf count)
647            ;; Every 400 requests we have to read the stream in
648            ;; order to avoid deadlocks.
649            (when (or (null articles)    ;All requests have been sent.
650                      (zerop (% count nntp-maximum-request)))
651              (nntp-accept-response)
652              (while (progn
653                       (set-buffer buf)
654                       (goto-char last-point)
655                       ;; Count replies.
656                       (while (nntp-next-result-arrived-p)
657                         (setq last-point (point))
658                         (incf received))
659                       (< received count))
660                ;; If number of headers is greater than 100, give
661                ;;  informative messages.
662                (and (numberp nntp-large-newsgroup)
663                     (> number nntp-large-newsgroup)
664                     (zerop (% received 20))
665                     (nnheader-message 6 "NNTP: Receiving headers... %d%%"
666                                       (/ (* received 100) number)))
667                (nntp-accept-response))))
668          (and (numberp nntp-large-newsgroup)
669               (> number nntp-large-newsgroup)
670               (nnheader-message 6 "NNTP: Receiving headers...done"))
671
672          ;; Now all of replies are received.  Fold continuation lines.
673          (nnheader-fold-continuation-lines)
674          ;; Remove all "\r"'s.
675          (nnheader-strip-cr)
676          (copy-to-buffer nntp-server-buffer (point-min) (point-max))
677          'headers)))))
678
679 (deffoo nntp-retrieve-groups (groups &optional server)
680   "Retrieve group info on GROUPS."
681   (nntp-with-open-group
682    nil server
683    (when (nntp-find-connection-buffer nntp-server-buffer)
684      (catch 'done
685        (save-excursion
686          ;; Erase nntp-server-buffer before nntp-inhibit-erase.
687          (set-buffer nntp-server-buffer)
688          (erase-buffer)
689          (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
690          ;; The first time this is run, this variable is `try'.  So we
691          ;; try.
692          (when (eq nntp-server-list-active-group 'try)
693            (nntp-try-list-active (car groups)))
694          (erase-buffer)
695          (let ((count 0)
696                (groups groups)
697                (received 0)
698                (last-point (point-min))
699                (nntp-inhibit-erase t)
700                (buf (nntp-find-connection-buffer nntp-server-buffer))
701                (command (if nntp-server-list-active-group
702                             "LIST ACTIVE" "GROUP")))
703            (while groups
704              ;; Timeout may have killed the buffer.
705              (unless (gnus-buffer-live-p buf)
706                (nnheader-report 'nntp "Connection to %s is closed." server)
707                (throw 'done nil))
708              ;; Send the command to the server.
709              (nntp-send-command nil command (pop groups))
710              (incf count)
711              ;; Every 400 requests we have to read the stream in
712              ;; order to avoid deadlocks.
713              (when (or (null groups)    ;All requests have been sent.
714                        (zerop (% count nntp-maximum-request)))
715                (nntp-accept-response)
716                (while (and (gnus-buffer-live-p buf)
717                            (progn
718                              ;; Search `blue moon' in this file for the
719                              ;; reason why set-buffer here.
720                              (set-buffer buf)
721                              (goto-char last-point)
722                              ;; Count replies.
723                              (while (re-search-forward "^[0-9]" nil t)
724                                (incf received))
725                              (setq last-point (point))
726                              (< received count)))
727                  (nntp-accept-response))))
728
729            ;; Wait for the reply from the final command.
730            (unless (gnus-buffer-live-p buf)
731              (nnheader-report 'nntp "Connection to %s is closed." server)
732              (throw 'done nil))
733            (set-buffer buf)
734            (goto-char (point-max))
735            (re-search-backward "^[0-9]" nil t)
736            (when (looking-at "^[23]")
737              (while (and (gnus-buffer-live-p buf)
738                          (progn
739                            (set-buffer buf)
740                            (goto-char (point-max))
741                            (if (not nntp-server-list-active-group)
742                                (not (re-search-backward "\r?\n"
743                                                         (- (point) 3) t))
744                              (not (re-search-backward "^\\.\r?\n"
745                                                       (- (point) 4) t)))))
746                (nntp-accept-response)))
747
748            ;; Now all replies are received.  We remove CRs.
749            (unless (gnus-buffer-live-p buf)
750              (nnheader-report 'nntp "Connection to %s is closed." server)
751              (throw 'done nil))
752            (set-buffer buf)
753            (goto-char (point-min))
754            (while (search-forward "\r" nil t)
755              (replace-match "" t t))
756
757            (if (not nntp-server-list-active-group)
758                (progn
759                  (copy-to-buffer nntp-server-buffer (point-min) (point-max))
760                  'group)
761              ;; We have read active entries, so we just delete the
762              ;; superfluous gunk.
763              (goto-char (point-min))
764              (while (re-search-forward "^[.2-5]" nil t)
765                (delete-region (match-beginning 0)
766                               (progn (forward-line 1) (point))))
767              (copy-to-buffer nntp-server-buffer (point-min) (point-max))
768              'active)))))))
769
770 (deffoo nntp-retrieve-articles (articles &optional group server)
771   (nntp-with-open-group
772     group server
773    (save-excursion
774      (let ((number (length articles))
775            (articles articles)
776            (count 0)
777            (received 0)
778            (last-point (point-min))
779            (buf (nntp-find-connection-buffer nntp-server-buffer))
780            (nntp-inhibit-erase t)
781            (map (apply 'vector articles))
782            (point 1)
783            article)
784        (set-buffer buf)
785        (erase-buffer)
786        ;; Send ARTICLE command.
787        (while (setq article (pop articles))
788          (nntp-send-command
789           nil
790           "ARTICLE" (if (numberp article)
791                         (int-to-string article)
792                       ;; `articles' is either a list of article numbers
793                       ;; or a list of article IDs.
794                       article))
795          (incf count)
796          ;; Every 400 requests we have to read the stream in
797          ;; order to avoid deadlocks.
798          (when (or (null articles)      ;All requests have been sent.
799                    (zerop (% count nntp-maximum-request)))
800            (nntp-accept-response)
801            (while (progn
802                     (set-buffer buf)
803                     (goto-char last-point)
804                     ;; Count replies.
805                     (while (nntp-next-result-arrived-p)
806                       (aset map received (cons (aref map received) (point)))
807                       (setq last-point (point))
808                       (incf received))
809                     (< received count))
810              ;; If number of headers is greater than 100, give
811              ;;  informative messages.
812              (and (numberp nntp-large-newsgroup)
813                   (> number nntp-large-newsgroup)
814                   (zerop (% received 20))
815                   (nnheader-message 6 "NNTP: Receiving articles... %d%%"
816                                     (/ (* received 100) number)))
817              (nntp-accept-response))))
818        (and (numberp nntp-large-newsgroup)
819             (> number nntp-large-newsgroup)
820             (nnheader-message 6 "NNTP: Receiving articles...done"))
821
822        ;; Now we have all the responses.  We go through the results,
823        ;; wash it and copy it over to the server buffer.
824        (set-buffer nntp-server-buffer)
825        (erase-buffer)
826        (setq last-point (point-min))
827        (mapcar
828         (lambda (entry)
829           (narrow-to-region
830            (setq point (goto-char (point-max)))
831            (progn
832              (insert-buffer-substring buf last-point (cdr entry))
833              (point-max)))
834           (setq last-point (cdr entry))
835           (nntp-decode-text)
836           (widen)
837           (cons (car entry) point))
838         map)))))
839
840 (defun nntp-try-list-active (group)
841   (nntp-list-active-group group)
842   (save-excursion
843     (set-buffer nntp-server-buffer)
844     (goto-char (point-min))
845     (cond ((or (eobp)
846                (looking-at "5[0-9]+"))
847            (setq nntp-server-list-active-group nil))
848           (t
849            (setq nntp-server-list-active-group t)))))
850
851 (deffoo nntp-list-active-group (group &optional server)
852   "Return the active info on GROUP (which can be a regexp)."
853   (nntp-with-open-group
854    nil server
855    (nntp-send-command "^\\.*\r?\n" "LIST ACTIVE" group)))
856
857 (deffoo nntp-request-group-articles (group &optional server)
858   "Return the list of existing articles in GROUP."
859   (nntp-with-open-group
860    nil server
861    (nntp-send-command "^\\.*\r?\n" "LISTGROUP" group)))
862
863 (deffoo nntp-request-article (article &optional group server buffer command)
864   (nntp-with-open-group
865     group server
866     (when (nntp-send-command-and-decode
867            "\r?\n\\.\r?\n" "ARTICLE"
868            (if (numberp article) (int-to-string article) article))
869       (if (and buffer
870                (not (equal buffer nntp-server-buffer)))
871           (save-excursion
872             (set-buffer nntp-server-buffer)
873             (copy-to-buffer buffer (point-min) (point-max))
874             (nntp-find-group-and-number group))
875         (nntp-find-group-and-number group)))))
876
877 (deffoo nntp-request-head (article &optional group server)
878   (nntp-with-open-group
879    group server
880    (when (nntp-send-command
881           "\r?\n\\.\r?\n" "HEAD"
882           (if (numberp article) (int-to-string article) article))
883      (prog1
884          (nntp-find-group-and-number group)
885        (nntp-decode-text)))))
886
887 (deffoo nntp-request-body (article &optional group server)
888   (nntp-with-open-group
889    group server
890    (nntp-send-command-and-decode
891     "\r?\n\\.\r?\n" "BODY"
892     (if (numberp article) (int-to-string article) article))))
893
894 (deffoo nntp-request-group (group &optional server dont-check)
895   (nntp-with-open-group 
896     nil server
897     (when (nntp-send-command "^[245].*\n" "GROUP" group)
898       (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
899         (setcar (cddr entry) group)))))
900
901 (deffoo nntp-close-group (group &optional server)
902   t)
903
904 (deffoo nntp-server-opened (&optional server)
905   "Say whether a connection to SERVER has been opened."
906   (and (nnoo-current-server-p 'nntp server)
907        nntp-server-buffer
908        (gnus-buffer-live-p nntp-server-buffer)
909        (nntp-find-connection nntp-server-buffer)))
910
911 (deffoo nntp-open-server (server &optional defs connectionless)
912   (nnheader-init-server-buffer)
913   (if (nntp-server-opened server)
914       t
915     (when (or (stringp (car defs))
916               (numberp (car defs)))
917       (setq defs (cons (list 'nntp-port-number (car defs)) (cdr defs))))
918     (unless (assq 'nntp-address defs)
919       (setq defs (append defs (list (list 'nntp-address server)))))
920     (nnoo-change-server 'nntp server defs)
921     (unless connectionless
922       (or (nntp-find-connection nntp-server-buffer)
923           (nntp-open-connection nntp-server-buffer)))))
924
925 (deffoo nntp-close-server (&optional server)
926   (nntp-possibly-change-group nil server t)
927   (let ((process (nntp-find-connection nntp-server-buffer)))
928     (while process
929       (when (memq (process-status process) '(open run))
930         (ignore-errors
931           (nntp-send-string process "QUIT")
932           (unless (eq nntp-open-connection-function 'nntp-open-network-stream)
933             ;; Ok, this is evil, but when using telnet and stuff
934             ;; as the connection method, it's important that the
935             ;; QUIT command actually is sent out before we kill
936             ;; the process.
937             (sleep-for 1))))
938       (nntp-kill-buffer (process-buffer process))
939       (setq process (car (pop nntp-connection-alist))))
940     (nnoo-close-server 'nntp)))
941
942 (deffoo nntp-request-close ()
943   (let (process)
944     (while (setq process (pop nntp-connection-list))
945       (when (memq (process-status process) '(open run))
946         (ignore-errors
947           (nntp-send-string process "QUIT")
948           (unless (eq nntp-open-connection-function 'nntp-open-network-stream)
949             ;; Ok, this is evil, but when using telnet and stuff
950             ;; as the connection method, it's important that the
951             ;; QUIT command actually is sent out before we kill
952             ;; the process.
953             (sleep-for 1))))
954       (nntp-kill-buffer (process-buffer process)))))
955
956 (deffoo nntp-request-list (&optional server)
957   (nntp-with-open-group
958    nil server
959    (nntp-send-command-and-decode "\r?\n\\.\r?\n" "LIST")))
960
961 (deffoo nntp-request-list-newsgroups (&optional server)
962   (nntp-with-open-group
963    nil server
964    (nntp-send-command "\r?\n\\.\r?\n" "LIST NEWSGROUPS")))
965
966 (deffoo nntp-request-newgroups (date &optional server)
967   (nntp-with-open-group
968    nil server
969    (save-excursion
970      (set-buffer nntp-server-buffer)
971      (let* ((time (date-to-time date))
972             (ls (- (cadr time) (nth 8 (decode-time time)))))
973        (cond ((< ls 0)
974               (setcar time (1- (car time)))
975               (setcar (cdr time) (+ ls 65536)))
976              ((>= ls 65536)
977               (setcar time (1+ (car time)))
978               (setcar (cdr time) (- ls 65536)))
979              (t
980               (setcar (cdr time) ls)))
981        (prog1
982            (nntp-send-command
983             "^\\.\r?\n" "NEWGROUPS"
984             (format-time-string "%y%m%d %H%M%S" time)
985             "GMT")
986          (nntp-decode-text))))))
987
988 (deffoo nntp-request-post (&optional server)
989   (nntp-with-open-group
990    nil server
991    (when (nntp-send-command "^[23].*\r?\n" "POST")
992      (let ((response (with-current-buffer nntp-server-buffer
993                        nntp-process-response))
994            server-id)
995        (when (and response
996                   (string-match "^[23].*\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)"
997                                 response))
998          (setq server-id (match-string 1 response))
999          (narrow-to-region (goto-char (point-min))
1000                            (if (search-forward "\n\n" nil t)
1001                                (1- (point))
1002                              (point-max)))
1003          (unless (mail-fetch-field "Message-ID")
1004            (goto-char (point-min))
1005            (insert "Message-ID: " server-id "\n"))
1006          (widen))
1007        (run-hooks 'nntp-prepare-post-hook)
1008        (nntp-send-buffer "^[23].*\n")))))
1009
1010 (deffoo nntp-request-type (group article)
1011   'news)
1012
1013 (deffoo nntp-asynchronous-p ()
1014   t)
1015
1016 ;;; Hooky functions.
1017
1018 (defun nntp-send-mode-reader ()
1019   "Send the MODE READER command to the nntp server.
1020 This function is supposed to be called from `nntp-server-opened-hook'.
1021 It will make innd servers spawn an nnrpd process to allow actual article
1022 reading."
1023   (nntp-send-command "^.*\n" "MODE READER"))
1024
1025 (defun nntp-send-authinfo (&optional send-if-force)
1026   "Send the AUTHINFO to the nntp server.
1027 It will look in the \"~/.authinfo\" file for matching entries.  If
1028 nothing suitable is found there, it will prompt for a user name
1029 and a password.
1030
1031 If SEND-IF-FORCE, only send authinfo to the server if the
1032 .authinfo file has the FORCE token."
1033   (let* ((list (gnus-parse-netrc nntp-authinfo-file))
1034          (alist (gnus-netrc-machine list nntp-address "nntp"))
1035          (force (gnus-netrc-get alist "force"))
1036          (user (or (gnus-netrc-get alist "login") nntp-authinfo-user))
1037          (passwd (gnus-netrc-get alist "password")))
1038     (when (or (not send-if-force)
1039               force)
1040       (unless user
1041         (setq user (read-string (format "NNTP (%s) user name: " nntp-address))
1042               nntp-authinfo-user user))
1043       (unless (member user '(nil ""))
1044         (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" user)
1045         (when t                         ;???Should check if AUTHINFO succeeded
1046           (nntp-send-command
1047            "^2.*\r?\n" "AUTHINFO PASS"
1048            (or passwd
1049                nntp-authinfo-password
1050                (setq nntp-authinfo-password
1051                      (mail-source-read-passwd
1052                       (format "NNTP (%s@%s) password: "
1053                               user nntp-address))))))))))
1054
1055 (defun nntp-send-nosy-authinfo ()
1056   "Send the AUTHINFO to the nntp server."
1057   (let ((user (read-string (format "NNTP (%s) user name: " nntp-address))))
1058     (unless (member user '(nil ""))
1059       (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" user)
1060       (when t                           ;???Should check if AUTHINFO succeeded
1061         (nntp-send-command "^2.*\r?\n" "AUTHINFO PASS"
1062                            (mail-source-read-passwd "NNTP (%s@%s) password: "
1063                                                     user nntp-address))))))
1064
1065 (defun nntp-send-authinfo-from-file ()
1066   "Send the AUTHINFO to the nntp server.
1067
1068 The authinfo login name is taken from the user's login name and the
1069 password contained in '~/.nntp-authinfo'."
1070   (when (file-exists-p "~/.nntp-authinfo")
1071     (with-temp-buffer
1072       (insert-file-contents "~/.nntp-authinfo")
1073       (goto-char (point-min))
1074       (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" (user-login-name))
1075       (nntp-send-command
1076        "^2.*\r?\n" "AUTHINFO PASS"
1077        (buffer-substring (point) (progn (end-of-line) (point)))))))
1078
1079 ;;; Internal functions.
1080
1081 (defun nntp-handle-authinfo (process)
1082   "Take care of an authinfo response from the server."
1083   (let ((last nntp-last-command))
1084     (funcall nntp-authinfo-function)
1085     ;; We have to re-send the function that was interrupted by
1086     ;; the authinfo request.
1087     (save-excursion
1088       (set-buffer nntp-server-buffer)
1089       (erase-buffer))
1090     (nntp-send-string process last)))
1091
1092 (defun nntp-make-process-buffer (buffer)
1093   "Create a new, fresh buffer usable for nntp process connections."
1094   (save-excursion
1095     (set-buffer
1096      (generate-new-buffer
1097       (format " *server %s %s %s*"
1098               nntp-address nntp-port-number
1099               (gnus-buffer-exists-p buffer))))
1100     (mm-enable-multibyte)
1101     (set (make-local-variable 'after-change-functions) nil)
1102     (set (make-local-variable 'nntp-process-wait-for) nil)
1103     (set (make-local-variable 'nntp-process-callback) nil)
1104     (set (make-local-variable 'nntp-process-to-buffer) nil)
1105     (set (make-local-variable 'nntp-process-start-point) nil)
1106     (set (make-local-variable 'nntp-process-decode) nil)
1107     (current-buffer)))
1108
1109 (defun nntp-open-connection (buffer)
1110   "Open a connection to PORT on ADDRESS delivering output to BUFFER."
1111   (run-hooks 'nntp-prepare-server-hook)
1112   (let* ((pbuffer (nntp-make-process-buffer buffer))
1113          (timer
1114           (and nntp-connection-timeout
1115                (nnheader-run-at-time
1116                 nntp-connection-timeout nil
1117                 `(lambda ()
1118                    (nntp-kill-buffer ,pbuffer)))))
1119          (process
1120           (condition-case ()
1121               (let ((coding-system-for-read nntp-coding-system-for-read)
1122                     (coding-system-for-write nntp-coding-system-for-write))
1123                 (funcall nntp-open-connection-function pbuffer))
1124             (error nil)
1125             (quit
1126              (message "Quit opening connection")
1127              (nntp-kill-buffer pbuffer)
1128              (signal 'quit nil)
1129              nil))))
1130     (when timer
1131       (nnheader-cancel-timer timer))
1132     (unless process
1133       (nntp-kill-buffer pbuffer))
1134     (when (and (buffer-name pbuffer)
1135                process)
1136       (process-kill-without-query process)
1137       (if (and (nntp-wait-for process "^2.*\n" buffer nil t)
1138                (memq (process-status process) '(open run)))
1139           (prog1
1140               (caar (push (list process buffer nil) nntp-connection-alist))
1141             (push process nntp-connection-list)
1142             (save-excursion
1143               (set-buffer pbuffer)
1144               (nntp-read-server-type)
1145               (erase-buffer)
1146               (set-buffer nntp-server-buffer)
1147               (let ((nnheader-callback-function nil))
1148                 (run-hooks 'nntp-server-opened-hook)
1149                 (nntp-send-authinfo t))))
1150         (nntp-kill-buffer (process-buffer process))
1151         nil))))
1152
1153 (defun nntp-open-network-stream (buffer)
1154   (open-network-stream "nntpd" buffer nntp-address nntp-port-number))
1155
1156 (defun nntp-open-ssl-stream (buffer)
1157   (let* ((process-connection-type nil)
1158          (proc (start-process "nntpd" buffer 
1159                               shell-file-name
1160                               shell-command-switch
1161                               (format-spec nntp-ssl-program 
1162                                            (format-spec-make
1163                                             ?s nntp-address
1164                                             ?p nntp-port-number)))))
1165     (process-kill-without-query proc)
1166     (save-excursion
1167       (set-buffer buffer)
1168       (nntp-wait-for-string "^\r*20[01]")
1169       (beginning-of-line)
1170       (delete-region (point-min) (point))
1171       proc)))
1172
1173 (defun nntp-read-server-type ()
1174   "Find out what the name of the server we have connected to is."
1175   ;; Wait for the status string to arrive.
1176   (setq nntp-server-type (buffer-string))
1177   (let ((alist nntp-server-action-alist)
1178         (case-fold-search t)
1179         entry)
1180     ;; Run server-specific commands.
1181     (while alist
1182       (setq entry (pop alist))
1183       (when (string-match (car entry) nntp-server-type)
1184         (if (and (listp (cadr entry))
1185                  (not (eq 'lambda (caadr entry))))
1186             (eval (cadr entry))
1187           (funcall (cadr entry)))))))
1188
1189 (defun nntp-async-wait (process wait-for buffer decode callback)
1190   (save-excursion
1191     (set-buffer (process-buffer process))
1192     (unless nntp-inside-change-function
1193       (erase-buffer))
1194     (setq nntp-process-wait-for wait-for
1195           nntp-process-to-buffer buffer
1196           nntp-process-decode decode
1197           nntp-process-callback callback
1198           nntp-process-start-point (point-max))
1199     (setq after-change-functions '(nntp-after-change-function))
1200     (if nntp-async-needs-kluge
1201         (nntp-async-kluge process))))
1202
1203 (defun nntp-async-kluge (process)
1204   ;; emacs 20.3 bug: process output with encoding 'binary
1205   ;; doesn't trigger after-change-functions.
1206   (unless nntp-async-timer
1207     (setq nntp-async-timer
1208           (nnheader-run-at-time 1 1 'nntp-async-timer-handler)))
1209   (add-to-list 'nntp-async-process-list process))
1210
1211 (defun nntp-async-timer-handler ()
1212   (mapcar
1213    (lambda (proc)
1214      (if (memq (process-status proc) '(open run))
1215          (nntp-async-trigger proc)
1216        (nntp-async-stop proc)))
1217    nntp-async-process-list))
1218
1219 (defun nntp-async-stop (proc)
1220   (setq nntp-async-process-list (delq proc nntp-async-process-list))
1221   (when (and nntp-async-timer (not nntp-async-process-list))
1222     (nnheader-cancel-timer nntp-async-timer)
1223     (setq nntp-async-timer nil)))
1224
1225 (defun nntp-after-change-function (beg end len)
1226   (unwind-protect
1227       ;; we only care about insertions at eob
1228       (when (and (eq 0 len) (eq (point-max) end))
1229         (save-match-data
1230           (let ((proc (get-buffer-process (current-buffer))))
1231             (when proc
1232               (nntp-async-trigger proc)))))
1233     ;; any throw from after-change-functions will leave it
1234     ;; set to nil.  so we reset it here, if necessary.
1235     (when quit-flag
1236       (setq after-change-functions '(nntp-after-change-function)))))
1237
1238 (defun nntp-async-trigger (process)
1239   (save-excursion
1240     (set-buffer (process-buffer process))
1241     (when nntp-process-callback
1242       ;; do we have an error message?
1243       (goto-char nntp-process-start-point)
1244       (if (memq (following-char) '(?4 ?5))
1245           ;; wants credentials?
1246           (if (looking-at "480")
1247               (nntp-handle-authinfo process)
1248             ;; report error message.
1249             (nntp-snarf-error-message)
1250             (nntp-do-callback nil))
1251
1252         ;; got what we expect?
1253         (goto-char (point-max))
1254         (when (re-search-backward
1255                nntp-process-wait-for nntp-process-start-point t)
1256           (let ((response (match-string 0)))
1257             (with-current-buffer nntp-server-buffer
1258               (setq nntp-process-response response)))
1259           (nntp-async-stop process)
1260           ;; convert it.
1261           (when (gnus-buffer-exists-p nntp-process-to-buffer)
1262             (let ((buf (current-buffer))
1263                   (start nntp-process-start-point)
1264                   (decode nntp-process-decode))
1265               (save-excursion
1266                 (set-buffer nntp-process-to-buffer)
1267                 (goto-char (point-max))
1268                 (save-restriction
1269                   (narrow-to-region (point) (point))
1270                   (insert-buffer-substring buf start)
1271                   (when decode
1272                     (nntp-decode-text))))))
1273           ;; report it.
1274           (goto-char (point-max))
1275           (nntp-do-callback
1276            (buffer-name (get-buffer nntp-process-to-buffer))))))))
1277
1278 (defun nntp-do-callback (arg)
1279   (let ((callback nntp-process-callback)
1280         (nntp-inside-change-function t))
1281     (setq nntp-process-callback nil)
1282     (funcall callback arg)))
1283
1284 (defun nntp-snarf-error-message ()
1285   "Save the error message in the current buffer."
1286   (let ((message (buffer-string)))
1287     (while (string-match "[\r\n]+" message)
1288       (setq message (replace-match " " t t message)))
1289     (nnheader-report 'nntp message)
1290     message))
1291
1292 (defun nntp-accept-process-output (process)
1293   "Wait for output from PROCESS and message some dots."
1294   (save-excursion
1295     (set-buffer (or (nntp-find-connection-buffer nntp-server-buffer)
1296                     nntp-server-buffer))
1297     (let ((len (/ (buffer-size) 1024))
1298           message-log-max)
1299       (unless (< len 10)
1300         (setq nntp-have-messaged t)
1301         (nnheader-message 7 "nntp read: %dk" len)))
1302     (accept-process-output
1303      process
1304      (truncate nntp-read-timeout)
1305      (truncate (* (- nntp-read-timeout
1306                      (truncate nntp-read-timeout))
1307                   1000)))
1308     ;; accept-process-output may update status of process to indicate
1309     ;; that the server has closed the connection.  This MUST be
1310     ;; handled here as the buffer restored by the save-excursion may
1311     ;; be the process's former output buffer (i.e. now killed)
1312     (or (and process 
1313              (memq (process-status process) '(open run)))
1314         (nntp-report "Server closed connection"))))
1315
1316 (defun nntp-accept-response ()
1317   "Wait for output from the process that outputs to BUFFER."
1318   (nntp-accept-process-output (nntp-find-connection nntp-server-buffer)))
1319
1320 (defun nntp-possibly-change-group (group server &optional connectionless)
1321   (let ((nnheader-callback-function nil))
1322     (when server
1323       (or (nntp-server-opened server)
1324           (nntp-open-server server nil connectionless)))
1325
1326     (unless connectionless
1327       (or (nntp-find-connection nntp-server-buffer)
1328           (nntp-open-connection nntp-server-buffer))))
1329
1330   (when group
1331     (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
1332       (when (not (equal group (caddr entry)))
1333         (save-excursion
1334           (set-buffer (process-buffer (car entry)))
1335           (erase-buffer)
1336           (nntp-send-command "^[245].*\n" "GROUP" group)
1337           (setcar (cddr entry) group)
1338           (erase-buffer)
1339           (save-excursion
1340             (set-buffer nntp-server-buffer)
1341             (erase-buffer)))))))
1342
1343 (defun nntp-decode-text (&optional cr-only)
1344   "Decode the text in the current buffer."
1345   (goto-char (point-min))
1346   (while (search-forward "\r" nil t)
1347     (delete-char -1))
1348   (unless cr-only
1349     ;; Remove trailing ".\n" end-of-transfer marker.
1350     (goto-char (point-max))
1351     (forward-line -1)
1352     (when (looking-at ".\n")
1353       (delete-char 2))
1354     ;; Delete status line.
1355     (goto-char (point-min))
1356     (while (looking-at "[1-5][0-9][0-9] .*\n")
1357       ;; For some unknown reason, there is more than one status line.
1358       (delete-region (point) (progn (forward-line 1) (point))))
1359     ;; Remove "." -> ".." encoding.
1360     (while (search-forward "\n.." nil t)
1361       (delete-char -1))))
1362
1363 (defun nntp-encode-text ()
1364   "Encode the text in the current buffer."
1365   (save-excursion
1366     ;; Replace "." at beginning of line with "..".
1367     (goto-char (point-min))
1368     (while (re-search-forward "^\\." nil t)
1369       (insert "."))
1370     (goto-char (point-max))
1371     ;; Insert newline at the end of the buffer.
1372     (unless (bolp)
1373       (insert "\n"))
1374     ;; Insert `.' at end of buffer (end of text mark).
1375     (goto-char (point-max))
1376     (insert ".\n")
1377     (goto-char (point-min))
1378     (while (not (eobp))
1379       (end-of-line)
1380       (delete-char 1)
1381       (insert nntp-end-of-line))))
1382
1383 (defun nntp-retrieve-headers-with-xover (articles &optional fetch-old)
1384   (set-buffer nntp-server-buffer)
1385   (erase-buffer)
1386   (cond
1387
1388    ;; This server does not talk NOV.
1389    ((not nntp-server-xover)
1390     nil)
1391
1392    ;; We don't care about gaps.
1393    ((or (not nntp-nov-gap)
1394         fetch-old)
1395     (nntp-send-xover-command
1396      (if fetch-old
1397          (if (numberp fetch-old)
1398              (max 1 (- (car articles) fetch-old))
1399            1)
1400        (car articles))
1401      (car (last articles)) 'wait)
1402
1403     (goto-char (point-min))
1404     (when (looking-at "[1-5][0-9][0-9] .*\n")
1405       (delete-region (point) (progn (forward-line 1) (point))))
1406     (while (search-forward "\r" nil t)
1407       (replace-match "" t t))
1408     (goto-char (point-max))
1409     (forward-line -1)
1410     (when (looking-at "\\.")
1411       (delete-region (point) (progn (forward-line 1) (point)))))
1412
1413    ;; We do it the hard way.  For each gap, an XOVER command is sent
1414    ;; to the server.  We do not wait for a reply from the server, we
1415    ;; just send them off as fast as we can.  That means that we have
1416    ;; to count the number of responses we get back to find out when we
1417    ;; have gotten all we asked for.
1418    ((numberp nntp-nov-gap)
1419     (let ((count 0)
1420           (received 0)
1421           last-point
1422           in-process-buffer-p
1423           (buf nntp-server-buffer)
1424           (process-buffer (nntp-find-connection-buffer nntp-server-buffer))
1425           first
1426           last)
1427       ;; We have to check `nntp-server-xover'.  If it gets set to nil,
1428       ;; that means that the server does not understand XOVER, but we
1429       ;; won't know that until we try.
1430       (while (and nntp-server-xover articles)
1431         (setq first (car articles))
1432         ;; Search forward until we find a gap, or until we run out of
1433         ;; articles.
1434         (while (and (cdr articles)
1435                     (< (- (nth 1 articles) (car articles)) nntp-nov-gap))
1436           (setq articles (cdr articles)))
1437
1438         (setq in-process-buffer-p (stringp nntp-server-xover))
1439         (nntp-send-xover-command first (setq last (car articles)))
1440         (setq articles (cdr articles))
1441
1442         (when (and nntp-server-xover in-process-buffer-p)
1443           ;; Don't count tried request.
1444           (setq count (1+ count))
1445
1446           ;; Every 400 requests we have to read the stream in
1447           ;; order to avoid deadlocks.
1448           (when (or (null articles)     ;All requests have been sent.
1449                     (= 1 (% count nntp-maximum-request)))
1450
1451             (nntp-accept-response)
1452             ;; On some Emacs versions the preceding function has a
1453             ;; tendency to change the buffer.  Perhaps.  It's quite
1454             ;; difficult to reproduce, because it only seems to happen
1455             ;; once in a blue moon.
1456             (set-buffer process-buffer)
1457             (while (progn
1458                      (goto-char (or last-point (point-min)))
1459                      ;; Count replies.
1460                      (while (re-search-forward "^[0-9][0-9][0-9] .*\n" nil t)
1461                        (incf received))
1462                      (setq last-point (point))
1463                      (or (< received count)
1464                          ;; I haven't started reading the final response
1465                          (progn
1466                            (goto-char (point-max))
1467                            (forward-line -1)
1468                            (not (looking-at "^\\.\r?\n")))))
1469               ;; I haven't read the end of the final response
1470               (nntp-accept-response)
1471               (set-buffer process-buffer))))
1472
1473         ;; Some nntp servers seem to have an extension to the XOVER
1474         ;; extension.  On these servers, requesting an article range
1475         ;; preceeding the active range does not return an error as
1476         ;; specified in the RFC.  What we instead get is the NOV entry
1477         ;; for the first available article.  Obviously, a client can
1478         ;; use that entry to avoid making unnecessary requests.  The
1479         ;; only problem is for a client that assumes that the response
1480         ;; will always be within the requested ranage.  For such a
1481         ;; client, we can get N copies of the same entry (one for each
1482         ;; XOVER command sent to the server).
1483
1484         (when (<= count 1)
1485           (goto-char (point-min))
1486           (when (re-search-forward "^[0-9][0-9][0-9] .*\n\\([0-9]+\\)" nil t)
1487             (let ((low-limit (string-to-int
1488                               (buffer-substring (match-beginning 1) 
1489                                                 (match-end 1)))))
1490               (while (and articles (<= (car articles) low-limit))
1491                 (setq articles (cdr articles))))))
1492         (set-buffer buf))
1493
1494       (when nntp-server-xover
1495         (when in-process-buffer-p
1496           (set-buffer buf)
1497           (goto-char (point-max))
1498           (insert-buffer-substring process-buffer)
1499           (set-buffer process-buffer)
1500           (erase-buffer)
1501           (set-buffer buf))
1502
1503         ;; We remove any "." lines and status lines.
1504         (goto-char (point-min))
1505         (while (search-forward "\r" nil t)
1506           (delete-char -1))
1507         (goto-char (point-min))
1508         (delete-matching-lines "^\\.$\\|^[1-5][0-9][0-9] ")
1509         t))))
1510
1511   nntp-server-xover)
1512
1513 (defun nntp-send-xover-command (beg end &optional wait-for-reply)
1514   "Send the XOVER command to the server."
1515   (let ((range (format "%d-%d" beg end))
1516         (nntp-inhibit-erase t))
1517     (if (stringp nntp-server-xover)
1518         ;; If `nntp-server-xover' is a string, then we just send this
1519         ;; command.
1520         (if wait-for-reply
1521             (nntp-send-command-nodelete
1522              "\r?\n\\.\r?\n" nntp-server-xover range)
1523           ;; We do not wait for the reply.
1524           (nntp-send-command-nodelete nil nntp-server-xover range))
1525       (let ((commands nntp-xover-commands))
1526         ;; `nntp-xover-commands' is a list of possible XOVER commands.
1527         ;; We try them all until we get at positive response.
1528         (while (and commands (eq nntp-server-xover 'try))
1529           (nntp-send-command-nodelete "\r?\n\\.\r?\n" (car commands) range)
1530           (save-excursion
1531             (set-buffer nntp-server-buffer)
1532             (goto-char (point-min))
1533             (and (looking-at "[23]")    ; No error message.
1534                  ;; We also have to look at the lines.  Some buggy
1535                  ;; servers give back simple lines with just the
1536                  ;; article number.  How... helpful.
1537                  (progn
1538                    (forward-line 1)
1539                    (looking-at "[0-9]+\t...")) ; More text after number.
1540                  (setq nntp-server-xover (car commands))))
1541           (setq commands (cdr commands)))
1542         ;; If none of the commands worked, we disable XOVER.
1543         (when (eq nntp-server-xover 'try)
1544           (save-excursion
1545             (set-buffer nntp-server-buffer)
1546             (erase-buffer)
1547             (setq nntp-server-xover nil)))
1548         nntp-server-xover))))
1549
1550 (defun nntp-find-group-and-number (&optional group)
1551   (save-excursion
1552     (save-restriction
1553       (set-buffer nntp-server-buffer)
1554       (narrow-to-region (goto-char (point-min))
1555                         (or (search-forward "\n\n" nil t) (point-max)))
1556       (goto-char (point-min))
1557       ;; We first find the number by looking at the status line.
1558       (let ((number (and (looking-at "2[0-9][0-9] +\\([0-9]+\\) ")
1559                          (string-to-int
1560                           (buffer-substring (match-beginning 1)
1561                                             (match-end 1)))))
1562             newsgroups xref)
1563         (and number (zerop number) (setq number nil))
1564         (if number
1565             ;; Then we find the group name.
1566             (setq group
1567                   (cond
1568                    ;; If there is only one group in the Newsgroups
1569                    ;; header, then it seems quite likely that this
1570                    ;; article comes from that group, I'd say.
1571                    ((and (setq newsgroups
1572                                (mail-fetch-field "newsgroups"))
1573                          (not (string-match "," newsgroups)))
1574                     newsgroups)
1575                    ;; If there is more than one group in the
1576                    ;; Newsgroups header, then the Xref header should
1577                    ;; be filled out.  We hazard a guess that the group
1578                    ;; that has this article number in the Xref header
1579                    ;; is the one we are looking for.  This might very
1580                    ;; well be wrong if this article happens to have
1581                    ;; the same number in several groups, but that's
1582                    ;; life.
1583                    ((and (setq xref (mail-fetch-field "xref"))
1584                          number
1585                          (string-match
1586                           (format "\\([^ :]+\\):%d" number) xref))
1587                     (match-string 1 xref))
1588                    (t "")))
1589           (cond
1590            ((and (setq xref (mail-fetch-field "xref"))
1591                  (string-match
1592                   (if group
1593                       (concat "\\(" (regexp-quote group) "\\):\\([0-9]+\\)")
1594                     "\\([^ :]+\\):\\([0-9]+\\)")
1595                   xref))
1596             (setq group (match-string 1 xref)
1597                   number (string-to-int (match-string 2 xref))))
1598            ((and (setq newsgroups
1599                        (mail-fetch-field "newsgroups"))
1600                  (not (string-match "," newsgroups)))
1601             (setq group newsgroups))
1602            (group)
1603            (t (setq group ""))))
1604         (when (string-match "\r" group)
1605           (setq group (substring group 0 (match-beginning 0))))
1606         (cons group number)))))
1607
1608 (defun nntp-wait-for-string (regexp)
1609   "Wait until string arrives in the buffer."
1610   (let ((buf (current-buffer))
1611         proc)
1612     (goto-char (point-min))
1613     (while (and (setq proc (get-buffer-process buf))
1614                 (memq (process-status proc) '(open run))
1615                 (not (re-search-forward regexp nil t)))
1616       (accept-process-output proc)
1617       (set-buffer buf)
1618       (goto-char (point-min)))))
1619
1620
1621 ;; ==========================================================================
1622 ;; Obsolete nntp-open-* connection methods -- drv
1623 ;; ==========================================================================
1624
1625 (defvoo nntp-open-telnet-envuser nil
1626   "*If non-nil, telnet session (client and server both) will support the ENVIRON option and not prompt for login name.")
1627
1628 (defvoo nntp-telnet-shell-prompt "bash\\|\$ *\r?$\\|> *\r?"
1629   "*Regular expression to match the shell prompt on the remote machine.")
1630
1631 (defvoo nntp-rlogin-program "rsh"
1632   "*Program used to log in on remote machines.
1633 The default is \"rsh\", but \"ssh\" is a popular alternative.")
1634
1635 (defvoo nntp-rlogin-parameters '("telnet" "-8" "${NNTPSERVER:=news}" "nntp")
1636   "*Parameters to `nntp-open-rlogin'.
1637 That function may be used as `nntp-open-connection-function'.  In that
1638 case, this list will be used as the parameter list given to rsh.")
1639
1640 (defvoo nntp-rlogin-user-name nil
1641   "*User name on remote system when using the rlogin connect method.")
1642
1643 (defvoo nntp-telnet-parameters
1644     '("exec" "telnet" "-8" "${NNTPSERVER:=news}" "nntp")
1645   "*Parameters to `nntp-open-telnet'.
1646 That function may be used as `nntp-open-connection-function'.  In that
1647 case, this list will be executed as a command after logging in
1648 via telnet.")
1649
1650 (defvoo nntp-telnet-user-name nil
1651   "User name to log in via telnet with.")
1652
1653 (defvoo nntp-telnet-passwd nil
1654   "Password to use to log in via telnet with.")
1655
1656 (defun nntp-open-telnet (buffer)
1657   (save-excursion
1658     (set-buffer buffer)
1659     (erase-buffer)
1660     (let ((proc (apply
1661                  'start-process
1662                  "nntpd" buffer nntp-telnet-command nntp-telnet-switches))
1663           (case-fold-search t))
1664       (when (memq (process-status proc) '(open run))
1665         (nntp-wait-for-string "^r?telnet")
1666         (process-send-string proc "set escape \^X\n")
1667         (cond
1668          ((and nntp-open-telnet-envuser nntp-telnet-user-name)
1669           (process-send-string proc (concat "open " "-l" nntp-telnet-user-name
1670                                             nntp-address "\n")))
1671          (t
1672           (process-send-string proc (concat "open " nntp-address "\n"))))
1673         (cond
1674          ((not nntp-open-telnet-envuser)
1675           (nntp-wait-for-string "^\r*.?login:")
1676           (process-send-string
1677            proc (concat
1678                  (or nntp-telnet-user-name
1679                      (setq nntp-telnet-user-name (read-string "login: ")))
1680                  "\n"))))
1681         (nntp-wait-for-string "^\r*.?password:")
1682         (process-send-string
1683          proc (concat
1684                (or nntp-telnet-passwd
1685                    (setq nntp-telnet-passwd
1686                          (mail-source-read-passwd "Password: ")))
1687                "\n"))
1688         (nntp-wait-for-string nntp-telnet-shell-prompt)
1689         (process-send-string
1690          proc (concat (mapconcat 'identity nntp-telnet-parameters " ") "\n"))
1691         (nntp-wait-for-string "^\r*20[01]")
1692         (beginning-of-line)
1693         (delete-region (point-min) (point))
1694         (process-send-string proc "\^]")
1695         (nntp-wait-for-string "^r?telnet")
1696         (process-send-string proc "mode character\n")
1697         (accept-process-output proc 1)
1698         (sit-for 1)
1699         (goto-char (point-min))
1700         (forward-line 1)
1701         (delete-region (point) (point-max)))
1702       proc)))
1703
1704 (defun nntp-open-rlogin (buffer)
1705   "Open a connection to SERVER using rsh."
1706   (let ((proc (if nntp-rlogin-user-name
1707                   (apply 'start-process
1708                          "nntpd" buffer nntp-rlogin-program
1709                          nntp-address "-l" nntp-rlogin-user-name
1710                          nntp-rlogin-parameters)
1711                 (apply 'start-process
1712                        "nntpd" buffer nntp-rlogin-program nntp-address
1713                        nntp-rlogin-parameters))))
1714     (save-excursion
1715       (set-buffer buffer)
1716       (nntp-wait-for-string "^\r*20[01]")
1717       (beginning-of-line)
1718       (delete-region (point-min) (point))
1719       proc)))
1720
1721
1722 ;; ==========================================================================
1723 ;; Replacements for the nntp-open-* functions -- drv
1724 ;; ==========================================================================
1725
1726 (defun nntp-open-telnet-stream (buffer)
1727   "Open a nntp connection by telnet'ing the news server.
1728
1729 Please refer to the following variables to customize the connection:
1730 - `nntp-pre-command',
1731 - `nntp-telnet-command',
1732 - `nntp-telnet-switches',
1733 - `nntp-address',
1734 - `nntp-port-number',
1735 - `nntp-end-of-line'."
1736   (let ((command `(,nntp-telnet-command
1737                    ,@nntp-telnet-switches
1738                    ,nntp-address ,nntp-port-number))
1739         proc)
1740     (and nntp-pre-command
1741          (push nntp-pre-command command))
1742     (setq proc (apply 'start-process "nntpd" buffer command))
1743     (save-excursion
1744       (set-buffer buffer)
1745       (nntp-wait-for-string "^\r*20[01]")
1746       (beginning-of-line)
1747       (delete-region (point-min) (point))
1748       proc)))
1749
1750 (defun nntp-open-via-rlogin-and-telnet (buffer)
1751   "Open a connection to an nntp server through an intermediate host.
1752 First rlogin to the remote host, and then telnet the real news server
1753 from there.
1754
1755 Please refer to the following variables to customize the connection:
1756 - `nntp-pre-command',
1757 - `nntp-via-rlogin-command',
1758 - `nntp-via-rlogin-command-switches',
1759 - `nntp-via-user-name',
1760 - `nntp-via-address',
1761 - `nntp-telnet-command',
1762 - `nntp-telnet-switches',
1763 - `nntp-address',
1764 - `nntp-port-number',
1765 - `nntp-end-of-line'."
1766   (let ((command `(,nntp-via-address
1767                    ,nntp-telnet-command
1768                    ,@nntp-telnet-switches))
1769         proc)
1770     (when nntp-via-user-name
1771       (setq command `("-l" ,nntp-via-user-name ,@command)))
1772     (when nntp-via-rlogin-command-switches
1773       (setq command (append nntp-via-rlogin-command-switches command)))
1774     (push nntp-via-rlogin-command command)
1775     (and nntp-pre-command
1776          (push nntp-pre-command command))
1777     (setq proc (apply 'start-process "nntpd" buffer command))
1778     (save-excursion
1779       (set-buffer buffer)
1780       (nntp-wait-for-string "^r?telnet")
1781       (process-send-string proc (concat "open " nntp-address
1782                                         " " nntp-port-number "\n"))
1783       (nntp-wait-for-string "^\r*20[01]")
1784       (beginning-of-line)
1785       (delete-region (point-min) (point))
1786       proc)))
1787
1788 (defun nntp-open-via-telnet-and-telnet (buffer)
1789   "Open a connection to an nntp server through an intermediate host.
1790 First telnet the remote host, and then telnet the real news server
1791 from there.
1792
1793 Please refer to the following variables to customize the connection:
1794 - `nntp-pre-command',
1795 - `nntp-via-telnet-command',
1796 - `nntp-via-telnet-switches',
1797 - `nntp-via-address',
1798 - `nntp-via-envuser',
1799 - `nntp-via-user-name',
1800 - `nntp-via-user-password',
1801 - `nntp-via-shell-prompt',
1802 - `nntp-telnet-command',
1803 - `nntp-telnet-switches',
1804 - `nntp-address',
1805 - `nntp-port-number',
1806 - `nntp-end-of-line'."
1807   (save-excursion
1808     (set-buffer buffer)
1809     (erase-buffer)
1810     (let ((command `(,nntp-via-telnet-command ,@nntp-via-telnet-switches))
1811           (case-fold-search t)
1812           proc)
1813       (and nntp-pre-command (push nntp-pre-command command))
1814       (setq proc (apply 'start-process "nntpd" buffer command))
1815       (when (memq (process-status proc) '(open run))
1816         (nntp-wait-for-string "^r?telnet")
1817         (process-send-string proc "set escape \^X\n")
1818         (cond
1819          ((and nntp-via-envuser nntp-via-user-name)
1820           (process-send-string proc (concat "open " "-l" nntp-via-user-name
1821                                             nntp-via-address "\n")))
1822          (t
1823           (process-send-string proc (concat "open " nntp-via-address
1824                                             "\n"))))
1825         (when (not nntp-via-envuser)
1826           (nntp-wait-for-string "^\r*.?login:")
1827           (process-send-string proc
1828                                (concat
1829                                 (or nntp-via-user-name
1830                                     (setq nntp-via-user-name
1831                                           (read-string "login: ")))
1832                                 "\n")))
1833         (nntp-wait-for-string "^\r*.?password:")
1834         (process-send-string proc
1835                              (concat
1836                               (or nntp-via-user-password
1837                                   (setq nntp-via-user-password
1838                                         (mail-source-read-passwd
1839                                          "Password: ")))
1840                               "\n"))
1841         (nntp-wait-for-string nntp-via-shell-prompt)
1842         (let ((real-telnet-command `("exec"
1843                                      ,nntp-telnet-command
1844                                      ,@nntp-telnet-switches
1845                                      ,nntp-address
1846                                      ,nntp-port-number)))
1847           (process-send-string proc
1848                                (concat (mapconcat 'identity
1849                                                   real-telnet-command " ")
1850                                        "\n")))
1851         (nntp-wait-for-string "^\r*20[01]")
1852         (beginning-of-line)
1853         (delete-region (point-min) (point))
1854         (process-send-string proc "\^]")
1855         (nntp-wait-for-string "^r?telnet")
1856         (process-send-string proc "mode character\n")
1857         (accept-process-output proc 1)
1858         (sit-for 1)
1859         (goto-char (point-min))
1860         (forward-line 1)
1861         (delete-region (point) (point-max)))
1862       proc)))
1863
1864 (provide 'nntp)
1865
1866 ;;; nntp.el ends here