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