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