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