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 (defvar nntp-with-open-group-first-pass nil)
556
557 (defmacro nntp-with-open-group (group server &optional connectionless &rest forms)
558   "Protect against servers that don't like clients that keep idle connections opens.  The problem
559 being that these servers may either close a connection or simply ignore any further requests on a
560 connection.  Closed connections are not detected until accept-process-output has updated the
561 process-status.  Dropped connections are not detected until the connection timeouts (which may be
562 several minutes) or nntp-connection-timeout has expired.  When these occur nntp-with-open-group,
563 opens a new connection then re-issues the NNTP command whose response triggered the error."
564   (when (and (listp connectionless)
565              (not (eq connectionless nil)))
566     (setq forms (cons connectionless forms)
567           connectionless nil))
568   `(let ((nntp-with-open-group-first-pass t)
569          nntp-with-open-group-internal)
570      (while (catch 'nntp-with-open-group-error
571               ;; Open the connection to the server
572               ;; NOTE: Existing connections are NOT tested.
573               (nntp-possibly-change-group ,group ,server ,connectionless)
574               
575               (let ((timer
576                      (and nntp-connection-timeout
577                           (nnheader-run-at-time
578                            nntp-connection-timeout nil
579                            '(lambda ()
580                               (let ((process (nntp-find-connection nntp-server-buffer))
581                                     (buffer  (and process (process-buffer process))))
582                                         ; when I an able to identify the connection to the server AND I've received NO 
583                                         ; reponse for nntp-connection-timeout seconds.
584                                 (when (and buffer (eq 0 (buffer-size buffer)))
585                                         ; Close the connection.  Take no other action as the accept input code will
586                                         ; handle the closed connection.
587                                   (nntp-kill-buffer buffer))))))))
588                 (unwind-protect
589                     (setq nntp-with-open-group-internal (progn ,@forms))
590                   (when timer
591                     (nnheader-cancel-timer timer)))
592                 nil))
593        (setq nntp-with-open-group-first-pass nil))
594      nntp-with-open-group-internal))
595
596 (defsubst nntp-report (&rest args)
597   "Report an error from the nntp backend.
598 The first string in ARGS can be a format string.
599 For some commands, the failed command may be retried once before actually displaying the error report."
600
601   (if nntp-with-open-group-first-pass
602       (throw 'nntp-with-open-group-error t))
603
604   (nnheader-report 'nntp args)
605   )
606
607 (deffoo nntp-retrieve-headers (articles &optional group server fetch-old)
608   "Retrieve the headers of ARTICLES."
609   (nntp-with-open-group
610     group server
611     (save-excursion
612       (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
613       (erase-buffer)
614       (if (and (not gnus-nov-is-evil)
615                (not nntp-nov-is-evil)
616                (nntp-retrieve-headers-with-xover articles fetch-old))
617           ;; We successfully retrieved the headers via XOVER.
618           'nov
619         ;; XOVER didn't work, so we do it the hard, slow and inefficient
620         ;; way.
621         (let ((number (length articles))
622               (count 0)
623               (received 0)
624               (last-point (point-min))
625               (buf (nntp-find-connection-buffer nntp-server-buffer))
626               (nntp-inhibit-erase t)
627               article)
628           ;; Send HEAD commands.
629           (while (setq article (pop articles))
630             (nntp-send-command
631              nil
632              "HEAD" (if (numberp article)
633                         (int-to-string article)
634                       ;; `articles' is either a list of article numbers
635                       ;; or a list of article IDs.
636                       article))
637             (incf count)
638             ;; Every 400 requests we have to read the stream in
639             ;; order to avoid deadlocks.
640             (when (or (null articles)   ;All requests have been sent.
641                       (zerop (% count nntp-maximum-request)))
642               (nntp-accept-response)
643               (while (progn
644                        (set-buffer buf)
645                        (goto-char last-point)
646                        ;; Count replies.
647                        (while (nntp-next-result-arrived-p)
648                          (setq last-point (point))
649                          (incf received))
650                        (< received count))
651                 ;; If number of headers is greater than 100, give
652                 ;;  informative messages.
653                 (and (numberp nntp-large-newsgroup)
654                      (> number nntp-large-newsgroup)
655                      (zerop (% received 20))
656                      (nnheader-message 6 "NNTP: Receiving headers... %d%%"
657                                        (/ (* received 100) number)))
658                 (nntp-accept-response))))
659           (and (numberp nntp-large-newsgroup)
660                (> number nntp-large-newsgroup)
661                (nnheader-message 6 "NNTP: Receiving headers...done"))
662
663           ;; Now all of replies are received.  Fold continuation lines.
664           (nnheader-fold-continuation-lines)
665           ;; Remove all "\r"'s.
666           (nnheader-strip-cr)
667           (copy-to-buffer nntp-server-buffer (point-min) (point-max))
668           'headers)))))
669
670 (deffoo nntp-retrieve-groups (groups &optional server)
671   "Retrieve group info on GROUPS."
672   (nntp-possibly-change-group nil server)
673   (when (nntp-find-connection-buffer nntp-server-buffer)
674     (catch 'done
675       (save-excursion
676         ;; Erase nntp-server-buffer before nntp-inhibit-erase.
677         (set-buffer nntp-server-buffer)
678         (erase-buffer)
679         (set-buffer (nntp-find-connection-buffer nntp-server-buffer))
680         ;; The first time this is run, this variable is `try'.  So we
681         ;; try.
682         (when (eq nntp-server-list-active-group 'try)
683           (nntp-try-list-active (car groups)))
684         (erase-buffer)
685         (let ((count 0)
686               (received 0)
687               (last-point (point-min))
688               (nntp-inhibit-erase t)
689               (buf (nntp-find-connection-buffer nntp-server-buffer))
690               (command (if nntp-server-list-active-group
691                            "LIST ACTIVE" "GROUP")))
692           (while groups
693             ;; Timeout may have killed the buffer.
694             (unless (gnus-buffer-live-p buf)
695               (nnheader-report 'nntp "Connection to %s is closed." server)
696               (throw 'done nil))
697             ;; Send the command to the server.
698             (nntp-send-command nil command (pop groups))
699             (incf count)
700             ;; Every 400 requests we have to read the stream in
701             ;; order to avoid deadlocks.
702             (when (or (null groups)     ;All requests have been sent.
703                       (zerop (% count nntp-maximum-request)))
704               (nntp-accept-response)
705               (while (and (gnus-buffer-live-p buf)
706                           (progn
707                             ;; Search `blue moon' in this file for the
708                             ;; reason why set-buffer here.
709                             (set-buffer buf)
710                             (goto-char last-point)
711                             ;; Count replies.
712                             (while (re-search-forward "^[0-9]" nil t)
713                               (incf received))
714                             (setq last-point (point))
715                             (< received count)))
716                 (nntp-accept-response))))
717
718           ;; Wait for the reply from the final command.
719           (unless (gnus-buffer-live-p buf)
720             (nnheader-report 'nntp "Connection to %s is closed." server)
721             (throw 'done nil))
722           (set-buffer buf)
723           (goto-char (point-max))
724           (re-search-backward "^[0-9]" nil t)
725           (when (looking-at "^[23]")
726             (while (and (gnus-buffer-live-p buf)
727                         (progn
728                           (set-buffer buf)
729                           (goto-char (point-max))
730                           (if (not nntp-server-list-active-group)
731                               (not (re-search-backward "\r?\n" (- (point) 3) t))
732                             (not (re-search-backward "^\\.\r?\n"
733                                                      (- (point) 4) t)))))
734               (nntp-accept-response)))
735
736           ;; Now all replies are received.  We remove CRs.
737           (unless (gnus-buffer-live-p buf)
738             (nnheader-report 'nntp "Connection to %s is closed." server)
739             (throw 'done nil))
740           (set-buffer buf)
741           (goto-char (point-min))
742           (while (search-forward "\r" nil t)
743             (replace-match "" t t))
744
745           (if (not nntp-server-list-active-group)
746               (progn
747                 (copy-to-buffer nntp-server-buffer (point-min) (point-max))
748                 'group)
749             ;; We have read active entries, so we just delete the
750             ;; superfluous gunk.
751             (goto-char (point-min))
752             (while (re-search-forward "^[.2-5]" nil t)
753               (delete-region (match-beginning 0)
754                              (progn (forward-line 1) (point))))
755             (copy-to-buffer nntp-server-buffer (point-min) (point-max))
756             'active))))))
757
758 (deffoo nntp-retrieve-articles (articles &optional group server)
759   (nntp-with-open-group 
760     group server
761    (save-excursion
762      (let ((number (length articles))
763            (count 0)
764            (received 0)
765            (last-point (point-min))
766            (buf (nntp-find-connection-buffer nntp-server-buffer))
767            (nntp-inhibit-erase t)
768            (map (apply 'vector articles))
769            (point 1)
770            article)
771        (set-buffer buf)
772        (erase-buffer)
773        ;; Send ARTICLE command.
774        (while (setq article (pop articles))
775          (nntp-send-command
776           nil
777           "ARTICLE" (if (numberp article)
778                         (int-to-string article)
779                       ;; `articles' is either a list of article numbers
780                       ;; or a list of article IDs.
781                       article))
782          (incf count)
783          ;; Every 400 requests we have to read the stream in
784          ;; order to avoid deadlocks.
785          (when (or (null articles)      ;All requests have been sent.
786                    (zerop (% count nntp-maximum-request)))
787            (nntp-accept-response)
788            (while (progn
789                     (set-buffer buf)
790                     (goto-char last-point)
791                     ;; Count replies.
792                     (while (nntp-next-result-arrived-p)
793                       (aset map received (cons (aref map received) (point)))
794                       (setq last-point (point))
795                       (incf received))
796                     (< received count))
797              ;; If number of headers is greater than 100, give
798              ;;  informative messages.
799              (and (numberp nntp-large-newsgroup)
800                   (> number nntp-large-newsgroup)
801                   (zerop (% received 20))
802                   (nnheader-message 6 "NNTP: Receiving articles... %d%%"
803                                     (/ (* received 100) number)))
804              (nntp-accept-response))))
805        (and (numberp nntp-large-newsgroup)
806             (> number nntp-large-newsgroup)
807             (nnheader-message 6 "NNTP: Receiving articles...done"))
808
809        ;; Now we have all the responses.  We go through the results,
810        ;; wash it and copy it over to the server buffer.
811        (set-buffer nntp-server-buffer)
812        (erase-buffer)
813        (setq last-point (point-min))
814        (mapcar
815         (lambda (entry)
816           (narrow-to-region
817            (setq point (goto-char (point-max)))
818            (progn
819              (insert-buffer-substring buf last-point (cdr entry))
820              (point-max)))
821           (setq last-point (cdr entry))
822           (nntp-decode-text)
823           (widen)
824           (cons (car entry) point))
825         map)))))
826
827 (defun nntp-try-list-active (group)
828   (nntp-list-active-group group)
829   (save-excursion
830     (set-buffer nntp-server-buffer)
831     (goto-char (point-min))
832     (cond ((or (eobp)
833                (looking-at "5[0-9]+"))
834            (setq nntp-server-list-active-group nil))
835           (t
836            (setq nntp-server-list-active-group t)))))
837
838 (deffoo nntp-list-active-group (group &optional server)
839   "Return the active info on GROUP (which can be a regexp)."
840   (nntp-possibly-change-group nil server)
841   (nntp-send-command "^\\.*\r?\n" "LIST ACTIVE" group))
842
843 (deffoo nntp-request-group-articles (group &optional server)
844   "Return the list of existing articles in GROUP."
845   (nntp-possibly-change-group nil server)
846   (nntp-send-command "^\\.*\r?\n" "LISTGROUP" group))
847
848 (deffoo nntp-request-article (article &optional group server buffer command)
849   (nntp-with-open-group 
850     group server
851     (when (nntp-send-command-and-decode
852            "\r?\n\\.\r?\n" "ARTICLE"
853            (if (numberp article) (int-to-string article) article))
854       (if (and buffer
855                (not (equal buffer nntp-server-buffer)))
856           (save-excursion
857             (set-buffer nntp-server-buffer)
858             (copy-to-buffer buffer (point-min) (point-max))
859             (nntp-find-group-and-number group))
860         (nntp-find-group-and-number group)))))
861
862 (deffoo nntp-request-head (article &optional group server)
863   (nntp-possibly-change-group group server)
864   (when (nntp-send-command
865          "\r?\n\\.\r?\n" "HEAD"
866          (if (numberp article) (int-to-string article) article))
867     (prog1
868         (nntp-find-group-and-number group)
869       (nntp-decode-text))))
870
871 (deffoo nntp-request-body (article &optional group server)
872   (nntp-possibly-change-group group server)
873   (nntp-send-command-and-decode
874    "\r?\n\\.\r?\n" "BODY"
875    (if (numberp article) (int-to-string article) article)))
876
877 (deffoo nntp-request-group (group &optional server dont-check)
878   (nntp-with-open-group 
879     nil server
880     (when (nntp-send-command "^[245].*\n" "GROUP" group)
881       (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
882         (setcar (cddr entry) group)))))
883
884 (deffoo nntp-close-group (group &optional server)
885   t)
886
887 (deffoo nntp-server-opened (&optional server)
888   "Say whether a connection to SERVER has been opened."
889   (and (nnoo-current-server-p 'nntp server)
890        nntp-server-buffer
891        (gnus-buffer-live-p nntp-server-buffer)
892        (nntp-find-connection nntp-server-buffer)))
893
894 (deffoo nntp-open-server (server &optional defs connectionless)
895   (nnheader-init-server-buffer)
896   (if (nntp-server-opened server)
897       t
898     (when (or (stringp (car defs))
899               (numberp (car defs)))
900       (setq defs (cons (list 'nntp-port-number (car defs)) (cdr defs))))
901     (unless (assq 'nntp-address defs)
902       (setq defs (append defs (list (list 'nntp-address server)))))
903     (nnoo-change-server 'nntp server defs)
904     (unless connectionless
905       (or (nntp-find-connection nntp-server-buffer)
906           (nntp-open-connection nntp-server-buffer)))))
907
908 (deffoo nntp-close-server (&optional server)
909   (nntp-possibly-change-group nil server t)
910   (let ((process (nntp-find-connection nntp-server-buffer)))
911     (while process
912       (when (memq (process-status process) '(open run))
913         (ignore-errors
914           (nntp-send-string process "QUIT")
915           (unless (eq nntp-open-connection-function 'nntp-open-network-stream)
916             ;; Ok, this is evil, but when using telnet and stuff
917             ;; as the connection method, it's important that the
918             ;; QUIT command actually is sent out before we kill
919             ;; the process.
920             (sleep-for 1))))
921       (nntp-kill-buffer (process-buffer process))
922       (setq process (car (pop nntp-connection-alist))))
923     (nnoo-close-server 'nntp)))
924
925 (deffoo nntp-request-close ()
926   (let (process)
927     (while (setq process (pop nntp-connection-list))
928       (when (memq (process-status process) '(open run))
929         (ignore-errors
930           (nntp-send-string process "QUIT")
931           (unless (eq nntp-open-connection-function 'nntp-open-network-stream)
932             ;; Ok, this is evil, but when using telnet and stuff
933             ;; as the connection method, it's important that the
934             ;; QUIT command actually is sent out before we kill
935             ;; the process.
936             (sleep-for 1))))
937       (nntp-kill-buffer (process-buffer process)))))
938
939 (deffoo nntp-request-list (&optional server)
940   "List active groups.  If `nntp-list-options' is non-nil, the listing
941 output from the server will be restricted to the specified newsgroups.
942 If `nntp-options-subscribe' is non-nil, remove newsgroups that do not
943 match the regexp.  If `nntp-options-not-subscribe' is non-nil, remove
944 newsgroups that match the regexp."
945   (nntp-possibly-change-group nil server)
946   (with-current-buffer nntp-server-buffer
947     (prog1
948         (if (not nntp-list-options)
949             (nntp-send-command-and-decode "\r?\n\\.\r?\n" "LIST")
950           (let ((options (if (consp nntp-list-options)
951                              nntp-list-options
952                            (list nntp-list-options)))
953                 (ret t))
954             (erase-buffer)
955             (while options
956               (goto-char (point-max))
957               (narrow-to-region (point) (point))
958               (setq ret (and ret
959                              (nntp-send-command-nodelete
960                               "\r?\n\\.\r?\n"
961                               (format "LIST ACTIVE %s" (car options))))
962                     options (cdr options))
963               (nntp-decode-text))
964             (widen)
965             ret))
966       (when (and (stringp nntp-options-subscribe)
967                  (not (string-equal "" nntp-options-subscribe)))
968         (goto-char (point-min))
969         (keep-lines nntp-options-subscribe))
970       (when (and (stringp nntp-options-not-subscribe)
971                  (not (string-equal "" nntp-options-not-subscribe)))
972         (goto-char (point-min))
973         (flush-lines nntp-options-subscribe)))))
974
975 (deffoo nntp-request-list-newsgroups (&optional server)
976   (nntp-possibly-change-group nil server)
977   (nntp-send-command "\r?\n\\.\r?\n" "LIST NEWSGROUPS"))
978
979 (deffoo nntp-request-newgroups (date &optional server)
980   (nntp-possibly-change-group nil server)
981   (save-excursion
982     (set-buffer nntp-server-buffer)
983     (let* ((time (date-to-time date))
984            (ls (- (cadr time) (nth 8 (decode-time time)))))
985       (cond ((< ls 0)
986              (setcar time (1- (car time)))
987              (setcar (cdr time) (+ ls 65536)))
988             ((>= ls 65536)
989              (setcar time (1+ (car time)))
990              (setcar (cdr time) (- ls 65536)))
991             (t
992              (setcar (cdr time) ls)))
993       (prog1
994           (nntp-send-command
995            "^\\.\r?\n" "NEWGROUPS"
996            (format-time-string "%y%m%d %H%M%S" time)
997            "GMT")
998         (nntp-decode-text)))))
999
1000 (deffoo nntp-request-post (&optional server)
1001   (nntp-possibly-change-group nil server)
1002   (when (nntp-send-command "^[23].*\r?\n" "POST")
1003     (let ((response (with-current-buffer nntp-server-buffer
1004                       nntp-process-response))
1005           server-id)
1006       (when (and response
1007                  (string-match "^[23].*\\(<[^\t\n @<>]+@[^\t\n @<>]+>\\)"
1008                                response))
1009         (setq server-id (match-string 1 response))
1010         (narrow-to-region (goto-char (point-min))
1011                           (if (search-forward "\n\n" nil t)
1012                               (1- (point))
1013                             (point-max)))
1014         (unless (mail-fetch-field "Message-ID")
1015           (goto-char (point-min))
1016           (insert "Message-ID: " server-id "\n"))
1017         (widen))
1018       (run-hooks 'nntp-prepare-post-hook)
1019       (nntp-send-buffer "^[23].*\n"))))
1020
1021 (deffoo nntp-request-type (group article)
1022   'news)
1023
1024 (deffoo nntp-asynchronous-p ()
1025   t)
1026
1027 ;;; Hooky functions.
1028
1029 (defun nntp-send-mode-reader ()
1030   "Send the MODE READER command to the nntp server.
1031 This function is supposed to be called from `nntp-server-opened-hook'.
1032 It will make innd servers spawn an nnrpd process to allow actual article
1033 reading."
1034   (nntp-send-command "^.*\n" "MODE READER"))
1035
1036 (defun nntp-send-authinfo (&optional send-if-force)
1037   "Send the AUTHINFO to the nntp server.
1038 It will look in the \"~/.authinfo\" file for matching entries.  If
1039 nothing suitable is found there, it will prompt for a user name
1040 and a password.
1041
1042 If SEND-IF-FORCE, only send authinfo to the server if the
1043 .authinfo file has the FORCE token."
1044   (let* ((list (gnus-parse-netrc nntp-authinfo-file))
1045          (alist (gnus-netrc-machine list nntp-address "nntp"))
1046          (force (gnus-netrc-get alist "force"))
1047          (user (or (gnus-netrc-get alist "login") nntp-authinfo-user))
1048          (passwd (gnus-netrc-get alist "password")))
1049     (when (or (not send-if-force)
1050               force)
1051       (unless user
1052         (setq user (read-string (format "NNTP (%s) user name: " nntp-address))
1053               nntp-authinfo-user user))
1054       (unless (member user '(nil ""))
1055         (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" user)
1056         (when t                         ;???Should check if AUTHINFO succeeded
1057           (nntp-send-command
1058            "^2.*\r?\n" "AUTHINFO PASS"
1059            (or passwd
1060                nntp-authinfo-password
1061                (setq nntp-authinfo-password
1062                      (mail-source-read-passwd
1063                       (format "NNTP (%s@%s) password: "
1064                               user nntp-address))))))))))
1065
1066 (defun nntp-send-nosy-authinfo ()
1067   "Send the AUTHINFO to the nntp server."
1068   (let ((user (read-string (format "NNTP (%s) user name: " nntp-address))))
1069     (unless (member user '(nil ""))
1070       (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" user)
1071       (when t                           ;???Should check if AUTHINFO succeeded
1072         (nntp-send-command "^2.*\r?\n" "AUTHINFO PASS"
1073                            (mail-source-read-passwd "NNTP (%s@%s) password: "
1074                                                     user nntp-address))))))
1075
1076 (defun nntp-send-authinfo-from-file ()
1077   "Send the AUTHINFO to the nntp server.
1078
1079 The authinfo login name is taken from the user's login name and the
1080 password contained in '~/.nntp-authinfo'."
1081   (when (file-exists-p "~/.nntp-authinfo")
1082     (with-temp-buffer
1083       (insert-file-contents "~/.nntp-authinfo")
1084       (goto-char (point-min))
1085       (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" (user-login-name))
1086       (nntp-send-command
1087        "^2.*\r?\n" "AUTHINFO PASS"
1088        (buffer-substring (point) (progn (end-of-line) (point)))))))
1089
1090 ;;; Internal functions.
1091
1092 (defun nntp-handle-authinfo (process)
1093   "Take care of an authinfo response from the server."
1094   (let ((last nntp-last-command))
1095     (funcall nntp-authinfo-function)
1096     ;; We have to re-send the function that was interrupted by
1097     ;; the authinfo request.
1098     (save-excursion
1099       (set-buffer nntp-server-buffer)
1100       (erase-buffer))
1101     (nntp-send-string process last)))
1102
1103 (defun nntp-make-process-buffer (buffer)
1104   "Create a new, fresh buffer usable for nntp process connections."
1105   (save-excursion
1106     (set-buffer
1107      (generate-new-buffer
1108       (format " *server %s %s %s*"
1109               nntp-address nntp-port-number
1110               (gnus-buffer-exists-p buffer))))
1111     (set (make-local-variable 'after-change-functions) nil)
1112     (set (make-local-variable 'nntp-process-wait-for) nil)
1113     (set (make-local-variable 'nntp-process-callback) nil)
1114     (set (make-local-variable 'nntp-process-to-buffer) nil)
1115     (set (make-local-variable 'nntp-process-start-point) nil)
1116     (set (make-local-variable 'nntp-process-decode) nil)
1117     (current-buffer)))
1118
1119 (defun nntp-open-connection (buffer)
1120   "Open a connection to PORT on ADDRESS delivering output to BUFFER."
1121   (run-hooks 'nntp-prepare-server-hook)
1122   (let* ((pbuffer (nntp-make-process-buffer buffer))
1123          (timer
1124           (and nntp-connection-timeout
1125                (nnheader-run-at-time
1126                 nntp-connection-timeout nil
1127                 `(lambda ()
1128                    (nntp-kill-buffer ,pbuffer)))))
1129          (process
1130           (condition-case ()
1131               (funcall nntp-open-connection-function pbuffer)
1132             (error nil)
1133             (quit
1134              (message "Quit opening connection")
1135              (nntp-kill-buffer pbuffer)
1136              (signal 'quit nil)
1137              nil))))
1138     (when timer
1139       (nnheader-cancel-timer timer))
1140     (unless process
1141       (nntp-kill-buffer pbuffer))
1142     (when (and (buffer-name pbuffer)
1143                process)
1144       (process-kill-without-query process)
1145       (if (and (nntp-wait-for process "^2.*\n" buffer nil t)
1146                (memq (process-status process) '(open run)))
1147           (prog1
1148               (caar (push (list process buffer nil) nntp-connection-alist))
1149             (push process nntp-connection-list)
1150             (save-excursion
1151               (set-buffer pbuffer)
1152               (nntp-read-server-type)
1153               (erase-buffer)
1154               (set-buffer nntp-server-buffer)
1155               (let ((nnheader-callback-function nil))
1156                 (run-hooks 'nntp-server-opened-hook)
1157                 (nntp-send-authinfo t))))
1158         (nntp-kill-buffer (process-buffer process))
1159         nil))))
1160
1161 (defun nntp-open-network-stream (buffer)
1162   (open-network-stream-as-binary
1163    "nntpd" buffer nntp-address nntp-port-number))
1164
1165 (defun nntp-open-ssl-stream (buffer)
1166   (let ((proc (open-ssl-stream "nntpd" buffer nntp-address nntp-port-number)))
1167     (save-excursion
1168       (set-buffer buffer)
1169       (nntp-wait-for-string "^\r*20[01]")
1170       (beginning-of-line)
1171       (delete-region (point-min) (point))
1172       proc)))
1173
1174 (defun nntp-read-server-type ()
1175   "Find out what the name of the server we have connected to is."
1176   ;; Wait for the status string to arrive.
1177   (setq nntp-server-type (buffer-string))
1178   (let ((alist nntp-server-action-alist)
1179         (case-fold-search t)
1180         entry)
1181     ;; Run server-specific commands.
1182     (while alist
1183       (setq entry (pop alist))
1184       (when (string-match (car entry) nntp-server-type)
1185         (if (and (listp (cadr entry))
1186                  (not (eq 'lambda (caadr entry))))
1187             (eval (cadr entry))
1188           (funcall (cadr entry)))))))
1189
1190 (defun nntp-async-wait (process wait-for buffer decode callback)
1191   (save-excursion
1192     (set-buffer (process-buffer process))
1193     (unless nntp-inside-change-function
1194       (erase-buffer))
1195     (setq nntp-process-wait-for wait-for
1196           nntp-process-to-buffer buffer
1197           nntp-process-decode decode
1198           nntp-process-callback callback
1199           nntp-process-start-point (point-max))
1200     (setq after-change-functions '(nntp-after-change-function))
1201     (if nntp-async-needs-kluge
1202         (nntp-async-kluge process))))
1203
1204 (defun nntp-async-kluge (process)
1205   ;; emacs 20.3 bug: process output with encoding 'binary
1206   ;; doesn't trigger after-change-functions.
1207   (unless nntp-async-timer
1208     (setq nntp-async-timer
1209           (nnheader-run-at-time 1 1 'nntp-async-timer-handler)))
1210   (add-to-list 'nntp-async-process-list process))
1211
1212 (defun nntp-async-timer-handler ()
1213   (mapcar
1214    (lambda (proc)
1215      (if (memq (process-status proc) '(open run))
1216          (nntp-async-trigger proc)
1217        (nntp-async-stop proc)))
1218    nntp-async-process-list))
1219
1220 (defun nntp-async-stop (proc)
1221   (setq nntp-async-process-list (delq proc nntp-async-process-list))
1222   (when (and nntp-async-timer (not nntp-async-process-list))
1223     (nnheader-cancel-timer nntp-async-timer)
1224     (setq nntp-async-timer nil)))
1225
1226 (defun nntp-after-change-function (beg end len)
1227   (unwind-protect
1228       ;; we only care about insertions at eob
1229       (when (and (eq 0 len) (eq (point-max) end))
1230         (save-match-data
1231           (let ((proc (get-buffer-process (current-buffer))))
1232             (when proc
1233               (nntp-async-trigger proc)))))
1234     ;; any throw from after-change-functions will leave it
1235     ;; set to nil.  so we reset it here, if necessary.
1236     (when quit-flag
1237       (setq after-change-functions '(nntp-after-change-function)))))
1238
1239 (defun nntp-async-trigger (process)
1240   (save-excursion
1241     (set-buffer (process-buffer process))
1242     (when nntp-process-callback
1243       ;; do we have an error message?
1244       (goto-char nntp-process-start-point)
1245       (if (memq (following-char) '(?4 ?5))
1246           ;; wants credentials?
1247           (if (looking-at "480")
1248               (nntp-handle-authinfo process)
1249             ;; report error message.
1250             (nntp-snarf-error-message)
1251             (nntp-do-callback nil))
1252
1253         ;; got what we expect?
1254         (goto-char (point-max))
1255         (when (re-search-backward
1256                nntp-process-wait-for nntp-process-start-point t)
1257           (let ((response (match-string 0)))
1258             (with-current-buffer nntp-server-buffer
1259               (setq nntp-process-response response)))
1260           (nntp-async-stop process)
1261           ;; convert it.
1262           (when (gnus-buffer-exists-p nntp-process-to-buffer)
1263             (let ((buf (current-buffer))
1264                   (start nntp-process-start-point)
1265                   (decode nntp-process-decode))
1266               (save-excursion
1267                 (set-buffer nntp-process-to-buffer)
1268                 (goto-char (point-max))
1269                 (save-restriction
1270                   (narrow-to-region (point) (point))
1271                   (insert-buffer-substring buf start)
1272                   (when decode
1273                     (nntp-decode-text))))))
1274           ;; report it.
1275           (goto-char (point-max))
1276           (nntp-do-callback
1277            (buffer-name (get-buffer nntp-process-to-buffer))))))))
1278
1279 (defun nntp-do-callback (arg)
1280   (let ((callback nntp-process-callback)
1281         (nntp-inside-change-function t))
1282     (setq nntp-process-callback nil)
1283     (funcall callback arg)))
1284
1285 (defun nntp-snarf-error-message ()
1286   "Save the error message in the current buffer."
1287   (let ((message (buffer-string)))
1288     (while (string-match "[\r\n]+" message)
1289       (setq message (replace-match " " t t message)))
1290     (nnheader-report 'nntp message)
1291     message))
1292
1293 (defun nntp-accept-process-output (process &optional timeout)
1294   "Wait for output from PROCESS and message some dots."
1295   (save-excursion
1296     (set-buffer (or (nntp-find-connection-buffer nntp-server-buffer)
1297                     nntp-server-buffer))
1298     (let ((len (/ (point-max) 1024))
1299           message-log-max)
1300       (unless (< len 10)
1301         (setq nntp-have-messaged t)
1302         (nnheader-message 7 "nntp read: %dk" len)))
1303     (accept-process-output process (or timeout 1))
1304     ;; accept-process-output may update status of process to indicate that the server has closed the
1305     ;; connection.  This MUST be handled here as the buffer restored by the save-excursion may be the 
1306     ;; process's former output buffer (i.e. now killed)
1307     (or (memq (process-status process) '(open run))
1308         (nntp-report "Server closed connection"))))
1309
1310 (defun nntp-accept-response ()
1311   "Wait for output from the process that outputs to BUFFER."
1312   (nntp-accept-process-output (nntp-find-connection nntp-server-buffer)))
1313
1314 (defun nntp-possibly-change-group (group server &optional connectionless)
1315   (let ((nnheader-callback-function nil))
1316     (when server
1317       (or (nntp-server-opened server)
1318           (nntp-open-server server nil connectionless)))
1319
1320     (unless connectionless
1321       (or (nntp-find-connection nntp-server-buffer)
1322           (nntp-open-connection nntp-server-buffer))))
1323
1324   (when group
1325     (let ((entry (nntp-find-connection-entry nntp-server-buffer)))
1326       (when (not (equal group (caddr entry)))
1327         (save-excursion
1328           (set-buffer (process-buffer (car entry)))
1329           (erase-buffer)
1330           (nntp-send-command "^[245].*\n" "GROUP" group)
1331           (setcar (cddr entry) group)
1332           (erase-buffer)
1333           (save-excursion
1334             (set-buffer nntp-server-buffer)
1335             (erase-buffer)))))))
1336
1337 (defun nntp-decode-text (&optional cr-only)
1338   "Decode the text in the current buffer."
1339   (goto-char (point-min))
1340   (while (search-forward "\r" nil t)
1341     (delete-char -1))
1342   (unless cr-only
1343     ;; Remove trailing ".\n" end-of-transfer marker.
1344     (goto-char (point-max))
1345     (forward-line -1)
1346     (when (looking-at ".\n")
1347       (delete-char 2))
1348     ;; Delete status line.
1349     (goto-char (point-min))
1350     (while (looking-at "[1-5][0-9][0-9] .*\n")
1351       ;; For some unknown reason, there is more than one status line.
1352       (delete-region (point) (progn (forward-line 1) (point))))
1353     ;; Remove "." -> ".." encoding.
1354     (while (search-forward "\n.." nil t)
1355       (delete-char -1))))
1356
1357 (defun nntp-encode-text ()
1358   "Encode the text in the current buffer."
1359   (save-excursion
1360     ;; Replace "." at beginning of line with "..".
1361     (goto-char (point-min))
1362     (while (re-search-forward "^\\." nil t)
1363       (insert "."))
1364     (goto-char (point-max))
1365     ;; Insert newline at the end of the buffer.
1366     (unless (bolp)
1367       (insert "\n"))
1368     ;; Insert `.' at end of buffer (end of text mark).
1369     (goto-char (point-max))
1370     (insert ".\n")
1371     (goto-char (point-min))
1372     (while (not (eobp))
1373       (end-of-line)
1374       (delete-char 1)
1375       (insert nntp-end-of-line))))
1376
1377 (defun nntp-retrieve-headers-with-xover (articles &optional fetch-old)
1378   (set-buffer nntp-server-buffer)
1379   (erase-buffer)
1380   (cond
1381
1382    ;; This server does not talk NOV.
1383    ((not nntp-server-xover)
1384     nil)
1385
1386    ;; We don't care about gaps.
1387    ((or (not nntp-nov-gap)
1388         fetch-old)
1389     (nntp-send-xover-command
1390      (if fetch-old
1391          (if (numberp fetch-old)
1392              (max 1 (- (car articles) fetch-old))
1393            1)
1394        (car articles))
1395      (car (last articles)) 'wait)
1396
1397     (goto-char (point-min))
1398     (when (looking-at "[1-5][0-9][0-9] .*\n")
1399       (delete-region (point) (progn (forward-line 1) (point))))
1400     (while (search-forward "\r" nil t)
1401       (replace-match "" t t))
1402     (goto-char (point-max))
1403     (forward-line -1)
1404     (when (looking-at "\\.")
1405       (delete-region (point) (progn (forward-line 1) (point)))))
1406
1407    ;; We do it the hard way.  For each gap, an XOVER command is sent
1408    ;; to the server.  We do not wait for a reply from the server, we
1409    ;; just send them off as fast as we can.  That means that we have
1410    ;; to count the number of responses we get back to find out when we
1411    ;; have gotten all we asked for.
1412    ((numberp nntp-nov-gap)
1413     (let ((count 0)
1414           (received 0)
1415           last-point
1416           in-process-buffer-p
1417           (buf nntp-server-buffer)
1418           (process-buffer (nntp-find-connection-buffer nntp-server-buffer))
1419           first
1420           last)
1421       ;; We have to check `nntp-server-xover'.  If it gets set to nil,
1422       ;; that means that the server does not understand XOVER, but we
1423       ;; won't know that until we try.
1424       (while (and nntp-server-xover articles)
1425         (setq first (car articles))
1426         ;; Search forward until we find a gap, or until we run out of
1427         ;; articles.
1428         (while (and (cdr articles)
1429                     (< (- (nth 1 articles) (car articles)) nntp-nov-gap))
1430           (setq articles (cdr articles)))
1431
1432         (setq in-process-buffer-p (stringp nntp-server-xover))
1433         (nntp-send-xover-command first (setq last (car articles)))
1434         (setq articles (cdr articles))
1435
1436         (when (and nntp-server-xover in-process-buffer-p)
1437           ;; Don't count tried request.
1438           (setq count (1+ count))
1439
1440           ;; Every 400 requests we have to read the stream in
1441           ;; order to avoid deadlocks.
1442           (when (or (null articles)     ;All requests have been sent.
1443                     (= 1 (% count nntp-maximum-request)))
1444
1445             (nntp-accept-response)
1446             ;; On some Emacs versions the preceding function has a
1447             ;; tendency to change the buffer.  Perhaps.  It's quite
1448             ;; difficult to reproduce, because it only seems to happen
1449             ;; once in a blue moon.
1450             (set-buffer process-buffer)
1451             (while (progn
1452                      (goto-char (or last-point (point-min)))
1453                      ;; Count replies.
1454                      (while (re-search-forward "^[0-9][0-9][0-9] .*\n" nil t)
1455                        (incf received))
1456                      (setq last-point (point))
1457                      (or (< received count) ;; I haven't started reading the final response
1458                          (progn
1459                            (goto-char (point-max))
1460                            (forward-line -1)
1461                            (not (looking-at "^\\.\r?\n"))) ;; I haven't read the end of the final response
1462                          ))
1463               (nntp-accept-response)
1464               (set-buffer process-buffer))))
1465
1466         ;; Some nntp servers seem to have an extension to the XOVER extension.  On these 
1467         ;; servers, requesting an article range preceeding the active range does not return an 
1468         ;; error as specified in the RFC.  What we instead get is the NOV entry for the first 
1469         ;; available article.  Obviously, a client can use that entry to avoid making unnecessary 
1470         ;; requests.  The only problem is for a client that assumes that the response will always be
1471         ;; within the requested ranage.  For such a client, we can get N copies of the same entry
1472         ;; (one for each XOVER command sent to the server).
1473
1474         (when (<= count 1)
1475           (goto-char (point-min))
1476           (when (re-search-forward "^[0-9][0-9][0-9] .*\n\\([0-9]+\\)" nil t)
1477             (let ((low-limit (string-to-int (buffer-substring (match-beginning 1) (match-end 1)))))
1478               (while (and articles (<= (car articles) low-limit))
1479                 (setq articles (cdr articles))))))
1480         (set-buffer buf))
1481
1482       (when nntp-server-xover
1483         (when in-process-buffer-p
1484           (set-buffer buf)
1485           (goto-char (point-max))
1486           (insert-buffer-substring process-buffer)
1487           (set-buffer process-buffer)
1488           (erase-buffer)
1489           (set-buffer buf))
1490
1491         ;; We remove any "." lines and status lines.
1492         (goto-char (point-min))
1493         (while (search-forward "\r" nil t)
1494           (delete-char -1))
1495         (goto-char (point-min))
1496         (delete-matching-lines "^\\.$\\|^[1-5][0-9][0-9] ")
1497         t))))
1498
1499   nntp-server-xover)
1500
1501 (defun nntp-send-xover-command (beg end &optional wait-for-reply)
1502   "Send the XOVER command to the server."
1503   (let ((range (format "%d-%d" beg end))
1504         (nntp-inhibit-erase t))
1505     (if (stringp nntp-server-xover)
1506         ;; If `nntp-server-xover' is a string, then we just send this
1507         ;; command.
1508         (if wait-for-reply
1509             (nntp-send-command-nodelete
1510              "\r?\n\\.\r?\n" nntp-server-xover range)
1511           ;; We do not wait for the reply.
1512           (nntp-send-command-nodelete nil nntp-server-xover range))
1513       (let ((commands nntp-xover-commands))
1514         ;; `nntp-xover-commands' is a list of possible XOVER commands.
1515         ;; We try them all until we get at positive response.
1516         (while (and commands (eq nntp-server-xover 'try))
1517           (nntp-send-command-nodelete "\r?\n\\.\r?\n" (car commands) range)
1518           (save-excursion
1519             (set-buffer nntp-server-buffer)
1520             (goto-char (point-min))
1521             (and (looking-at "[23]")    ; No error message.
1522                  ;; We also have to look at the lines.  Some buggy
1523                  ;; servers give back simple lines with just the
1524                  ;; article number.  How... helpful.
1525                  (progn
1526                    (forward-line 1)
1527                    (looking-at "[0-9]+\t...")) ; More text after number.
1528                  (setq nntp-server-xover (car commands))))
1529           (setq commands (cdr commands)))
1530         ;; If none of the commands worked, we disable XOVER.
1531         (when (eq nntp-server-xover 'try)
1532           (save-excursion
1533             (set-buffer nntp-server-buffer)
1534             (erase-buffer)
1535             (setq nntp-server-xover nil)))
1536         nntp-server-xover))))
1537
1538 (defun nntp-find-group-and-number (&optional group)
1539   (save-excursion
1540     (save-restriction
1541       (set-buffer nntp-server-buffer)
1542       (narrow-to-region (goto-char (point-min))
1543                         (or (search-forward "\n\n" nil t) (point-max)))
1544       (goto-char (point-min))
1545       ;; We first find the number by looking at the status line.
1546       (let ((number (and (looking-at "2[0-9][0-9] +\\([0-9]+\\) ")
1547                          (string-to-int
1548                           (buffer-substring (match-beginning 1)
1549                                             (match-end 1)))))
1550             newsgroups xref)
1551         (and number (zerop number) (setq number nil))
1552         (if number
1553             ;; Then we find the group name.
1554             (setq group
1555                   (cond
1556                    ;; If there is only one group in the Newsgroups
1557                    ;; header, then it seems quite likely that this
1558                    ;; article comes from that group, I'd say.
1559                    ((and (setq newsgroups
1560                                (mail-fetch-field "newsgroups"))
1561                          (not (string-match "," newsgroups)))
1562                     newsgroups)
1563                    ;; If there is more than one group in the
1564                    ;; Newsgroups header, then the Xref header should
1565                    ;; be filled out.  We hazard a guess that the group
1566                    ;; that has this article number in the Xref header
1567                    ;; is the one we are looking for.  This might very
1568                    ;; well be wrong if this article happens to have
1569                    ;; the same number in several groups, but that's
1570                    ;; life.
1571                    ((and (setq xref (mail-fetch-field "xref"))
1572                          number
1573                          (string-match
1574                           (format "\\([^ :]+\\):%d" number) xref))
1575                     (match-string 1 xref))
1576                    (t "")))
1577           (cond
1578            ((and (setq xref (mail-fetch-field "xref"))
1579                  (string-match
1580                   (if group
1581                       (concat "\\(" (regexp-quote group) "\\):\\([0-9]+\\)")
1582                     "\\([^ :]+\\):\\([0-9]+\\)")
1583                   xref))
1584             (setq group (match-string 1 xref)
1585                   number (string-to-int (match-string 2 xref))))
1586            ((and (setq newsgroups
1587                        (mail-fetch-field "newsgroups"))
1588                  (not (string-match "," newsgroups)))
1589             (setq group newsgroups))
1590            (group)
1591            (t (setq group ""))))
1592         (when (string-match "\r" group)
1593           (setq group (substring group 0 (match-beginning 0))))
1594         (cons group number)))))
1595
1596 (defun nntp-wait-for-string (regexp)
1597   "Wait until string arrives in the buffer."
1598   (let ((buf (current-buffer))
1599         proc)
1600     (goto-char (point-min))
1601     (while (and (setq proc (get-buffer-process buf))
1602                 (memq (process-status proc) '(open run))
1603                 (not (re-search-forward regexp nil t)))
1604       (accept-process-output proc)
1605       (set-buffer buf)
1606       (goto-char (point-min)))))
1607
1608
1609 ;; ==========================================================================
1610 ;; Obsolete nntp-open-* connection methods -- drv
1611 ;; ==========================================================================
1612
1613 (defvoo nntp-open-telnet-envuser nil
1614   "*If non-nil, telnet session (client and server both) will support the ENVIRON option and not prompt for login name.")
1615
1616 (defvoo nntp-telnet-shell-prompt "bash\\|\$ *\r?$\\|> *\r?"
1617   "*Regular expression to match the shell prompt on the remote machine.")
1618
1619 (defvoo nntp-rlogin-program "rsh"
1620   "*Program used to log in on remote machines.
1621 The default is \"rsh\", but \"ssh\" is a popular alternative.")
1622
1623 (defvoo nntp-rlogin-parameters '("telnet" "-8" "${NNTPSERVER:=news}" "nntp")
1624   "*Parameters to `nntp-open-rlogin'.
1625 That function may be used as `nntp-open-connection-function'.  In that
1626 case, this list will be used as the parameter list given to rsh.")
1627
1628 (defvoo nntp-rlogin-user-name nil
1629   "*User name on remote system when using the rlogin connect method.")
1630
1631 (defvoo nntp-telnet-parameters
1632     '("exec" "telnet" "-8" "${NNTPSERVER:=news}" "nntp")
1633   "*Parameters to `nntp-open-telnet'.
1634 That function may be used as `nntp-open-connection-function'.  In that
1635 case, this list will be executed as a command after logging in
1636 via telnet.")
1637
1638 (defvoo nntp-telnet-user-name nil
1639   "User name to log in via telnet with.")
1640
1641 (defvoo nntp-telnet-passwd nil
1642   "Password to use to log in via telnet with.")
1643
1644 (defun nntp-open-telnet (buffer)
1645   (save-excursion
1646     (set-buffer buffer)
1647     (erase-buffer)
1648     (let ((proc (as-binary-process
1649                  (apply
1650                   'start-process
1651                   "nntpd" buffer nntp-telnet-command nntp-telnet-switches)))
1652           (case-fold-search t))
1653       (when (memq (process-status proc) '(open run))
1654         (nntp-wait-for-string "^r?telnet")
1655         (process-send-string proc "set escape \^X\n")
1656         (cond
1657          ((and nntp-open-telnet-envuser nntp-telnet-user-name)
1658           (process-send-string proc (concat "open " "-l" nntp-telnet-user-name
1659                                             nntp-address "\n")))
1660          (t
1661           (process-send-string proc (concat "open " nntp-address "\n"))))
1662         (cond
1663          ((not nntp-open-telnet-envuser)
1664           (nntp-wait-for-string "^\r*.?login:")
1665           (process-send-string
1666            proc (concat
1667                  (or nntp-telnet-user-name
1668                      (setq nntp-telnet-user-name (read-string "login: ")))
1669                  "\n"))))
1670         (nntp-wait-for-string "^\r*.?password:")
1671         (process-send-string
1672          proc (concat
1673                (or nntp-telnet-passwd
1674                    (setq nntp-telnet-passwd
1675                          (mail-source-read-passwd "Password: ")))
1676                "\n"))
1677         (nntp-wait-for-string nntp-telnet-shell-prompt)
1678         (process-send-string
1679          proc (concat (mapconcat 'identity nntp-telnet-parameters " ") "\n"))
1680         (nntp-wait-for-string "^\r*20[01]")
1681         (beginning-of-line)
1682         (delete-region (point-min) (point))
1683         (process-send-string proc "\^]")
1684         (nntp-wait-for-string "^r?telnet")
1685         (process-send-string proc "mode character\n")
1686         (accept-process-output proc 1)
1687         (sit-for 1)
1688         (goto-char (point-min))
1689         (forward-line 1)
1690         (delete-region (point) (point-max)))
1691       proc)))
1692
1693 (defun nntp-open-rlogin (buffer)
1694   "Open a connection to SERVER using rsh."
1695   (let ((proc (if nntp-rlogin-user-name
1696                   (as-binary-process
1697                    (apply 'start-process
1698                           "nntpd" buffer nntp-rlogin-program
1699                           nntp-address "-l" nntp-rlogin-user-name
1700                           nntp-rlogin-parameters))
1701                 (as-binary-process
1702                  (apply 'start-process
1703                         "nntpd" buffer nntp-rlogin-program nntp-address
1704                         nntp-rlogin-parameters)))))
1705     (save-excursion
1706       (set-buffer buffer)
1707       (nntp-wait-for-string "^\r*20[01]")
1708       (beginning-of-line)
1709       (delete-region (point-min) (point))
1710       proc)))
1711
1712
1713 ;; ==========================================================================
1714 ;; Replacements for the nntp-open-* functions -- drv
1715 ;; ==========================================================================
1716
1717 (defun nntp-open-telnet-stream (buffer)
1718   "Open a nntp connection by telnet'ing the news server.
1719
1720 Please refer to the following variables to customize the connection:
1721 - `nntp-pre-command',
1722 - `nntp-telnet-command',
1723 - `nntp-telnet-switches',
1724 - `nntp-address',
1725 - `nntp-port-number',
1726 - `nntp-end-of-line'."
1727   (let ((command `(,nntp-telnet-command
1728                    ,@nntp-telnet-switches
1729                    ,nntp-address ,nntp-port-number))
1730         proc)
1731     (and nntp-pre-command
1732          (push nntp-pre-command command))
1733     (setq proc (apply 'start-process "nntpd" buffer command))
1734     (save-excursion
1735       (set-buffer buffer)
1736       (nntp-wait-for-string "^\r*20[01]")
1737       (beginning-of-line)
1738       (delete-region (point-min) (point))
1739       proc)))
1740
1741 (defun nntp-open-via-rlogin-and-telnet (buffer)
1742   "Open a connection to an nntp server through an intermediate host.
1743 First rlogin to the remote host, and then telnet the real news server
1744 from there.
1745
1746 Please refer to the following variables to customize the connection:
1747 - `nntp-pre-command',
1748 - `nntp-via-rlogin-command',
1749 - `nntp-via-rlogin-command-switches',
1750 - `nntp-via-user-name',
1751 - `nntp-via-address',
1752 - `nntp-telnet-command',
1753 - `nntp-telnet-switches',
1754 - `nntp-address',
1755 - `nntp-port-number',
1756 - `nntp-end-of-line'."
1757   (let ((command `(,nntp-via-address
1758                    ,nntp-telnet-command
1759                    ,@nntp-telnet-switches))
1760         proc)
1761     (when nntp-via-user-name
1762       (setq command `("-l" ,nntp-via-user-name ,@command)))
1763     (when nntp-via-rlogin-command-switches
1764       (setq command (append nntp-via-rlogin-command-switches command)))
1765     (push nntp-via-rlogin-command command)
1766     (and nntp-pre-command
1767          (push nntp-pre-command command))
1768     (setq proc (as-binary-process
1769                 (apply 'start-process "nntpd" buffer command)))
1770     (save-excursion
1771       (set-buffer buffer)
1772       (nntp-wait-for-string "^r?telnet")
1773       (process-send-string proc (concat "open " nntp-address
1774                                         " " nntp-port-number "\n"))
1775       (nntp-wait-for-string "^\r*20[01]")
1776       (beginning-of-line)
1777       (delete-region (point-min) (point))
1778       proc)))
1779
1780 (defun nntp-open-via-telnet-and-telnet (buffer)
1781   "Open a connection to an nntp server through an intermediate host.
1782 First telnet the remote host, and then telnet the real news server
1783 from there.
1784
1785 Please refer to the following variables to customize the connection:
1786 - `nntp-pre-command',
1787 - `nntp-via-telnet-command',
1788 - `nntp-via-telnet-switches',
1789 - `nntp-via-address',
1790 - `nntp-via-envuser',
1791 - `nntp-via-user-name',
1792 - `nntp-via-user-password',
1793 - `nntp-via-shell-prompt',
1794 - `nntp-telnet-command',
1795 - `nntp-telnet-switches',
1796 - `nntp-address',
1797 - `nntp-port-number',
1798 - `nntp-end-of-line'."
1799   (save-excursion
1800     (set-buffer buffer)
1801     (erase-buffer)
1802     (let ((command `(,nntp-via-telnet-command ,@nntp-via-telnet-switches))
1803           (case-fold-search t)
1804           proc)
1805       (and nntp-pre-command (push nntp-pre-command command))
1806       (setq proc (apply 'start-process "nntpd" buffer command))
1807       (when (memq (process-status proc) '(open run))
1808         (nntp-wait-for-string "^r?telnet")
1809         (process-send-string proc "set escape \^X\n")
1810         (cond
1811          ((and nntp-via-envuser nntp-via-user-name)
1812           (process-send-string proc (concat "open " "-l" nntp-via-user-name
1813                                             nntp-via-address "\n")))
1814          (t
1815           (process-send-string proc (concat "open " nntp-via-address
1816                                             "\n"))))
1817         (when (not nntp-via-envuser)
1818           (nntp-wait-for-string "^\r*.?login:")
1819           (process-send-string proc
1820                                (concat
1821                                 (or nntp-via-user-name
1822                                     (setq nntp-via-user-name
1823                                           (read-string "login: ")))
1824                                 "\n")))
1825         (nntp-wait-for-string "^\r*.?password:")
1826         (process-send-string proc
1827                              (concat
1828                               (or nntp-via-user-password
1829                                   (setq nntp-via-user-password
1830                                         (mail-source-read-passwd
1831                                          "Password: ")))
1832                               "\n"))
1833         (nntp-wait-for-string nntp-via-shell-prompt)
1834         (let ((real-telnet-command `("exec"
1835                                      ,nntp-telnet-command
1836                                      ,@nntp-telnet-switches
1837                                      ,nntp-address
1838                                      ,nntp-port-number)))
1839           (process-send-string proc
1840                                (concat (mapconcat 'identity
1841                                                   real-telnet-command " ")
1842                                        "\n")))
1843         (nntp-wait-for-string "^\r*20[01]")
1844         (beginning-of-line)
1845         (delete-region (point-min) (point))
1846         (process-send-string proc "\^]")
1847         (nntp-wait-for-string "^r?telnet")
1848         (process-send-string proc "mode character\n")
1849         (accept-process-output proc 1)
1850         (sit-for 1)
1851         (goto-char (point-min))
1852         (forward-line 1)
1853         (delete-region (point) (point-max)))
1854       proc)))
1855
1856 (provide 'nntp)
1857
1858 ;;; nntp.el ends here