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