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