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