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