+(defvar nntp-async-timer nil)
+(defvar nntp-async-process-list nil)
+
+(defvar nntp-ssl-program
+ "openssl s_client -quiet -ssl3 -connect %s:%p"
+"A string containing commands for SSL connections.
+Within a string, %s is replaced with the server address and %p with
+port number on server. The program should accept IMAP commands on
+stdin and return responses to stdout.")
+
+(defvar nntp-authinfo-rejected nil
+"A custom error condition used to report 'Authentication Rejected' errors.
+Condition handlers that match just this condition ensure that the nntp
+backend doesn't catch this error.")
+(put 'nntp-authinfo-rejected 'error-conditions '(error nntp-authinfo-rejected))
+(put 'nntp-authinfo-rejected 'error-message "Authorization Rejected")
+
+\f
+
+;;; Internal functions.
+
+(defsubst nntp-send-string (process string)
+ "Send STRING to PROCESS."
+ ;; We need to store the time to provide timeouts, and
+ ;; to store the command so the we can replay the command
+ ;; if the server gives us an AUTHINFO challenge.
+ (setq nntp-last-command-time (current-time)
+ nntp-last-command string)
+ (when nntp-record-commands
+ (nntp-record-command string))
+ (process-send-string process (concat string nntp-end-of-line))
+ (or (memq (process-status process) '(open run))
+ (nntp-report "Server closed connection")))
+
+(defun nntp-record-command (string)
+ "Record the command STRING."
+ (with-current-buffer (get-buffer-create "*nntp-log*")
+ (goto-char (point-max))
+ (let ((time (current-time)))
+ (insert (format-time-string "%Y%m%dT%H%M%S" time)
+ "." (format "%03d" (/ (nth 2 time) 1000))
+ " " nntp-address " " string "\n"))))
+
+(defun nntp-report (&rest args)
+ "Report an error from the nntp backend. The first string in ARGS
+can be a format string. For some commands, the failed command may be
+retried once before actually displaying the error report."
+
+ (when nntp-record-commands
+ (nntp-record-command "*** CALLED nntp-report ***"))
+
+ (nnheader-report 'nntp args)
+
+ (apply 'error args))
+
+(defun nntp-report-1 (&rest args)
+ "Throws out to nntp-with-open-group-error so that the connection may
+be restored and the command retried."
+
+ (when nntp-record-commands
+ (nntp-record-command "*** CONNECTION LOST ***"))
+
+ (throw 'nntp-with-open-group-error t))
+
+(defmacro nntp-insert-buffer-substring (buffer &optional start end)
+ "Copy string from unibyte buffer to multibyte current buffer."
+ (if (featurep 'xemacs)
+ `(insert-buffer-substring ,buffer ,start ,end)
+ `(if enable-multibyte-characters
+ (insert (with-current-buffer ,buffer
+ (mm-string-to-multibyte
+ ,(if (or start end)
+ `(buffer-substring (or ,start (point-min))
+ (or ,end (point-max)))
+ '(buffer-string)))))
+ (insert-buffer-substring ,buffer ,start ,end))))
+
+(defmacro nntp-copy-to-buffer (buffer start end)
+ "Copy string from unibyte current buffer to multibyte buffer."
+ (if (featurep 'xemacs)
+ `(copy-to-buffer ,buffer ,start ,end)
+ `(let ((string (buffer-substring ,start ,end)))
+ (with-current-buffer ,buffer
+ (erase-buffer)
+ (insert (if enable-multibyte-characters
+ (mm-string-to-multibyte string)
+ string))
+ (goto-char (point-min))
+ nil))))
+
+(defsubst nntp-wait-for (process wait-for buffer &optional decode discard)
+ "Wait for WAIT-FOR to arrive from PROCESS."
+
+ (with-current-buffer (process-buffer process)
+ (goto-char (point-min))
+
+ (while (and (or (not (memq (char-after (point)) '(?2 ?3 ?4 ?5)))
+ (looking-at "48[02]"))
+ (memq (process-status process) '(open run)))
+ (cond ((looking-at "480")
+ (nntp-handle-authinfo process))
+ ((looking-at "482")
+ (nnheader-report 'nntp (get 'nntp-authinfo-rejected 'error-message))
+ (signal 'nntp-authinfo-rejected nil))
+ ((looking-at "^.*\n")
+ (delete-region (point) (progn (forward-line 1) (point)))))
+ (nntp-accept-process-output process)
+ (goto-char (point-min)))
+ (prog1
+ (cond
+ ((looking-at "[45]")
+ (progn
+ (nntp-snarf-error-message)
+ nil))
+ ((not (memq (process-status process) '(open run)))
+ (nntp-report "Server closed connection"))
+ (t
+ (goto-char (point-max))
+ (let ((limit (point-min))
+ response)
+ (while (not (re-search-backward wait-for limit t))
+ (nntp-accept-process-output process)
+ ;; We assume that whatever we wait for is less than 1000
+ ;; characters long.
+ (setq limit (max (- (point-max) 1000) (point-min)))
+ (goto-char (point-max)))
+ (setq response (match-string 0))
+ (with-current-buffer nntp-server-buffer
+ (setq nntp-process-response response)))
+ (nntp-decode-text (not decode))
+ (unless discard
+ (with-current-buffer buffer
+ (goto-char (point-max))
+ (nntp-insert-buffer-substring (process-buffer process))
+ ;; Nix out "nntp reading...." message.
+ (when nntp-have-messaged
+ (setq nntp-have-messaged nil)
+ (nnheader-message 5 ""))))
+ t))
+ (unless discard
+ (erase-buffer)))))
+
+(defun nntp-kill-buffer (buffer)
+ (when (buffer-name buffer)
+ (kill-buffer buffer)
+ (nnheader-init-server-buffer)))
+
+(defun nntp-erase-buffer (buffer)
+ "Erase contents of BUFFER."
+ (with-current-buffer buffer
+ (erase-buffer)))
+
+(defsubst nntp-find-connection (buffer)
+ "Find the connection delivering to BUFFER."
+ (let ((alist nntp-connection-alist)
+ (buffer (if (stringp buffer) (get-buffer buffer) buffer))
+ process entry)
+ (while (and alist (setq entry (pop alist)))
+ (when (eq buffer (cadr entry))
+ (setq process (car entry)
+ alist nil)))
+ (when process
+ (if (memq (process-status process) '(open run))
+ process
+ (nntp-kill-buffer (process-buffer process))
+ (setq nntp-connection-alist (delq entry nntp-connection-alist))
+ nil))))
+
+(defsubst nntp-find-connection-entry (buffer)
+ "Return the entry for the connection to BUFFER."
+ (assq (nntp-find-connection buffer) nntp-connection-alist))
+
+(defun nntp-find-connection-buffer (buffer)
+ "Return the process connection buffer tied to BUFFER."
+ (let ((process (nntp-find-connection buffer)))
+ (when process
+ (process-buffer process))))
+
+(defsubst nntp-retrieve-data (command address port buffer
+ &optional wait-for callback decode)
+ "Use COMMAND to retrieve data into BUFFER from PORT on ADDRESS."
+ (let ((process (or (nntp-find-connection buffer)
+ (nntp-open-connection buffer))))
+ (if process
+ (progn
+ (unless (or nntp-inhibit-erase nnheader-callback-function)
+ (nntp-erase-buffer (process-buffer process)))
+ (condition-case err
+ (progn
+ (when command
+ (nntp-send-string process command))
+ (cond
+ ((eq callback 'ignore)
+ t)
+ ((and callback wait-for)
+ (nntp-async-wait process wait-for buffer decode callback)
+ t)
+ (wait-for
+ (nntp-wait-for process wait-for buffer decode))
+ (t t)))
+ (nntp-authinfo-rejected
+ (signal 'nntp-authinfo-rejected (cdr err)))
+ (error
+ (nnheader-report 'nntp "Couldn't open connection to %s: %s"
+ address err))
+ (quit
+ (message "Quit retrieving data from nntp")
+ (signal 'quit nil)
+ nil)))
+ (nnheader-report 'nntp "Couldn't open connection to %s" address))))
+
+(defsubst nntp-send-command (wait-for &rest strings)
+ "Send STRINGS to server and wait until WAIT-FOR returns."
+ (when (and (not nnheader-callback-function)
+ (not nntp-inhibit-output))
+ (nntp-erase-buffer nntp-server-buffer))
+ (let* ((command (mapconcat 'identity strings " "))
+ (process (nntp-find-connection nntp-server-buffer))
+ (buffer (and process (process-buffer process)))
+ (pos (and buffer (with-current-buffer buffer (point)))))
+ (if process
+ (prog1
+ (nntp-retrieve-data command
+ nntp-address nntp-port-number
+ nntp-server-buffer
+ wait-for nnheader-callback-function)
+ ;; If nothing to wait for, still remove possibly echo'ed commands.
+ ;; We don't have echoes if `nntp-never-echoes-commands' is non-nil
+ ;; or the value of `nntp-open-connection-function' is in
+ ;; `nntp-open-connection-functions-never-echo-commands', so we
+ ;; skip this in that cases.
+ (unless (or wait-for
+ nntp-never-echoes-commands
+ (memq
+ nntp-open-connection-function
+ nntp-open-connection-functions-never-echo-commands))
+ (nntp-accept-response)
+ (with-current-buffer buffer
+ (goto-char pos)
+ (if (looking-at (regexp-quote command))
+ (delete-region pos (progn (forward-line 1)
+ (point-at-bol)))))))
+ (nnheader-report 'nntp "Couldn't open connection to %s."
+ nntp-address))))
+
+(defun nntp-send-command-nodelete (wait-for &rest strings)
+ "Send STRINGS to server and wait until WAIT-FOR returns."
+ (let* ((command (mapconcat 'identity strings " "))
+ (process (nntp-find-connection nntp-server-buffer))
+ (buffer (and process (process-buffer process)))
+ (pos (and buffer (with-current-buffer buffer (point)))))
+ (if process
+ (prog1
+ (nntp-retrieve-data command
+ nntp-address nntp-port-number
+ nntp-server-buffer
+ wait-for nnheader-callback-function)
+ ;; If nothing to wait for, still remove possibly echo'ed commands
+ (unless wait-for
+ (nntp-accept-response)
+ (with-current-buffer buffer
+ (goto-char pos)
+ (if (looking-at (regexp-quote command))
+ (delete-region pos (progn (forward-line 1)
+ (point-at-bol)))))))
+ (nnheader-report 'nntp "Couldn't open connection to %s."
+ nntp-address))))
+
+(defun nntp-send-command-and-decode (wait-for &rest strings)
+ "Send STRINGS to server and wait until WAIT-FOR returns."
+ (when (and (not nnheader-callback-function)
+ (not nntp-inhibit-output))
+ (nntp-erase-buffer nntp-server-buffer))
+ (let* ((command (mapconcat 'identity strings " "))
+ (process (nntp-find-connection nntp-server-buffer))
+ (buffer (and process (process-buffer process)))
+ (pos (and buffer (with-current-buffer buffer (point)))))
+ (if process
+ (prog1
+ (nntp-retrieve-data command
+ nntp-address nntp-port-number
+ nntp-server-buffer
+ wait-for nnheader-callback-function t)
+ ;; If nothing to wait for, still remove possibly echo'ed commands
+ (unless wait-for
+ (nntp-accept-response)
+ (with-current-buffer buffer
+ (goto-char pos)
+ (if (looking-at (regexp-quote command))
+ (delete-region pos (progn (forward-line 1) (point-at-bol))))
+ )))
+ (nnheader-report 'nntp "Couldn't open connection to %s."
+ nntp-address))))
+
+
+(defun nntp-send-buffer (wait-for)
+ "Send the current buffer to server and wait until WAIT-FOR returns."
+ (when (and (not nnheader-callback-function)
+ (not nntp-inhibit-output))
+ (nntp-erase-buffer
+ (nntp-find-connection-buffer nntp-server-buffer)))
+ (nntp-encode-text)
+ ;; Make sure we did not forget to encode some of the content.
+ (assert (save-excursion (goto-char (point-min))
+ (not (re-search-forward "[^\000-\377]" nil t))))
+ (mm-disable-multibyte)
+ (process-send-region (nntp-find-connection nntp-server-buffer)
+ (point-min) (point-max))
+ (nntp-retrieve-data
+ nil nntp-address nntp-port-number nntp-server-buffer
+ wait-for nnheader-callback-function))