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