If STARTTLS failed, then just open a normal connection.
[gnus] / lisp / proto-stream.el
index 226157a..c1acf4b 100644 (file)
@@ -36,7 +36,7 @@
 
 ;; Usage example:
 
-;; (open-proto-stream
+;; (open-protocol-stream
 ;;  "*nnimap*" buffer address port
 ;;  :type 'network
 ;;  :capability-command "1 CAPABILITY\r\n"
@@ -44,7 +44,7 @@
 ;;  (lambda (capabilities)
 ;;    (if (not (string-match "STARTTLS" capabilities))
 ;;        nil
-;;      "1 STARTTLS")))
+;;      "1 STARTTLS\r\n")))
 
 ;;; Code:
 
 (require 'starttls)
 (require 'format-spec)
 
+(defcustom proto-stream-always-use-starttls (fboundp 'open-gnutls-stream)
+  "If non-nil, always try to upgrade network connections with STARTTLS."
+  :version "24.1"
+  :type 'boolean
+  :group 'comm)
+
 (declare-function gnutls-negotiate "gnutls"
                  (proc type &optional priority-string trustfiles keyfiles))
 
 ;;;###autoload
-(defun open-proto-stream (name buffer host service &rest parameters)
+(defun open-protocol-stream (name buffer host service &rest parameters)
   "Open a network stream to HOST.
 The first four parameters have the same meaning as in
 `open-network-stream'.  The function returns a list where the
@@ -72,6 +78,9 @@ values:
 :type -- either `network', `tls', `shell' or `starttls'.  If
 omitted, the default is `network'.
 
+:end-of-command -- a regexp saying what the end of a command is.
+This defaults to \"\\n\".
+
 :capability-command -- a string representing the command used to
 query server for capabilities.  For instance, for IMAP this is
 \"1 CAPABILITY\\r\\n\".
@@ -80,7 +89,7 @@ query server for capabilities.  For instance, for IMAP this is
 is the response to the capaibility command.  It should return nil
 if it turns out that the server doesn't support STARTTLS, or the
 command to switch on STARTTLS otherwise."
-  (let ((type (or (cadr (memq :type parameters)) 'stream)))
+  (let ((type (or (cadr (memq :type parameters)) 'network)))
     (when (and (eq type 'starttls)
               (fboundp 'open-gnutls-stream))
       (setq type 'network))
@@ -99,57 +108,78 @@ command to switch on STARTTLS otherwise."
   (let* ((start (with-current-buffer buffer (point)))
         (stream (open-network-stream name buffer host service))
         (capability-command (cadr (memq :capability-command parameters)))
-        (greeting (proto-stream-get-response stream start)))
+        (eoc (proto-stream-eoc parameters))
+        (type (cadr (memq :type parameters)))
+        (greeting (proto-stream-get-response stream start eoc)))
     (if (not capability-command)
        (list stream greeting nil)
       (let* ((capabilities
-             (proto-stream-capabilities stream capability-command))
+             (proto-stream-command stream capability-command eoc))
             (starttls-command
              (funcall (cadr (memq :starttls-function parameters))
                       capabilities)))
        (cond
-        ((not starttls-command)
+        ((or (not starttls-command)
+             (and (not (eq type 'starttls))
+                  (not proto-stream-always-use-starttls)))
          ;; If this server doesn't support STARTTLS, but we have
          ;; requested it explicitly, then close the connection and
          ;; return nil.
-         (if (eq (cadr (memq :type parameters)) 'starttls)
+         (if (eq type 'starttls)
              (progn
                (delete-process stream)
                nil)
            ;; Otherwise, just return this plain network connection.
            (list stream greeting capabilities)))
-        ((fboundp 'open-gnutls-stream)
-         (setq start (with-current-buffer buffer (point-max)))
-         (process-send-string stream starttls-command)
-         (proto-stream-get-response stream start)
-         (gnutls-negotiate stream nil)
+        ((or (fboundp 'open-gnutls-stream)
+             (executable-find "gnutls-cli"))
+         (unless (fboundp 'open-gnutls-stream)
+           (delete-process stream)
+           (let* ((starttls-use-gnutls t)
+                  (starttls-extra-arguments
+                   (if (eq type 'starttls)
+                       ;; When doing opportunistic TLS upgrades we
+                       ;; don't really care about the identity of the
+                       ;; peer.
+                       (cons "--insecure" starttls-extra-arguments)
+                     starttls-extra-arguments)))
+             (setq stream (starttls-open-stream name buffer host service)))
+           (proto-stream-get-response stream start eoc))
+         (proto-stream-command stream starttls-command eoc)
+         (if (fboundp 'open-gnutls-stream)
+             (gnutls-negotiate stream nil)
+           (unless (starttls-negotiate stream)
+             (delete-process stream)
+             (setq stream nil)))
+         (when (or (null stream)
+                   (not (memq (process-status stream)
+                              '(open run))))
+           ;; It didn't successfully negotiate STARTTLS, so we reopen
+           ;; the connection.
+           (setq stream (open-network-stream name buffer host service))
+           (proto-stream-get-response stream start eoc))
          ;; Re-get the capabilities, since they may have changed
          ;; after switching to TLS.
-         (setq start (with-current-buffer buffer (point-max)))
-         (process-send-string stream capability-command)
-         (list stream greeting (proto-stream-get-response stream start)))
-        (t
+         (list stream greeting
+               (proto-stream-command stream capability-command eoc)))
+        ((eq (cadr (memq :type parameters)) 'starttls)
          (delete-process stream)
-         (proto-stream-open-starttls name buffer host service parameters)))))))
+         nil)
+        (t
+         (list stream greeting capabilities)))))))
 
-(defun proto-stream-capabilities (stream command)
+(defun proto-stream-command (stream command eoc)
   (let ((start (with-current-buffer (process-buffer stream) (point-max))))
     (process-send-string stream command)
-    (proto-stream-get-response stream start)))
-
-(defun proto-stream-open-starttls (name buffer host service parameters)
-  (proto-stream-capability-open
-   (with-current-buffer buffer (point))
-   (starttls-open-stream name buffer host service)
-   parameters))
+    (proto-stream-get-response stream start eoc)))
 
-(defun proto-stream-get-response (stream start)
+(defun proto-stream-get-response (stream start end-of-command)
   (with-current-buffer (process-buffer stream)
     (save-excursion
       (goto-char start)
       (while (and (memq (process-status stream)
                        '(open run))
-                 (not (search-forward "\n" nil t)))
+                 (not (re-search-forward end-of-command nil t)))
        (accept-process-output stream 0 50)
        (goto-char start))
       (if (= start (point))
@@ -159,13 +189,23 @@ command to switch on STARTTLS otherwise."
        (buffer-substring start (point))))))
 
 (defun proto-stream-open-tls (name buffer host service parameters)
-  (proto-stream-capability-open
-   (with-current-buffer buffer (point))
-   (funcall (if (fboundp 'open-gnutls-stream)
-               'open-gnutls-stream
-             'open-tls-stream)
-           name buffer host service)
-   parameters))
+  (with-current-buffer buffer
+    (let ((start (point-max))
+         (stream
+          (funcall (if (fboundp 'open-gnutls-stream)
+                       'open-gnutls-stream
+                     'open-tls-stream)
+                   name buffer host service)))
+      ;; If we're using tls.el, we have to delete the output from
+      ;; openssl/gnutls-cli.
+      (unless (fboundp 'open-gnutls-stream)
+       (proto-stream-get-response
+        stream start (proto-stream-eoc parameters))
+       (goto-char (point-min))
+       (when (re-search-forward (proto-stream-eoc parameters) nil t)
+         (goto-char (match-beginning 0))
+         (delete-region (point-min) (line-beginning-position))))
+      (proto-stream-capability-open start stream parameters))))
 
 (defun proto-stream-open-shell (name buffer host service parameters)
   (proto-stream-capability-open
@@ -182,10 +222,16 @@ command to switch on STARTTLS otherwise."
 
 (defun proto-stream-capability-open (start stream parameters)
   (let ((capability-command (cadr (memq :capability-command parameters)))
-       (greeting (proto-stream-get-response stream start)))
+       (greeting (proto-stream-get-response
+                  stream start (proto-stream-eoc parameters))))
     (list stream greeting
          (and capability-command
-              (proto-stream-capabilities stream capability-command)))))
+              (proto-stream-command
+               stream capability-command (proto-stream-eoc parameters))))))
+
+(defun proto-stream-eoc (parameters)
+  (or (cadr (memq :end-of-command parameters))
+      "\r\n"))
 
 (provide 'proto-stream)