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