;;; network-stream.el --- open network processes, possibly with encryption
-;; Copyright (C) 2010-2011 Free Software Foundation, Inc.
+;; Copyright (C) 2010-2014 Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
;; Keywords: network
(require 'tls)
(require 'starttls)
+(require 'auth-source)
-(declare-function gnutls-negotiate "gnutls" t t) ; defun*
+(autoload 'gnutls-negotiate "gnutls")
+(autoload 'open-gnutls-stream "gnutls")
;;;###autoload
(defun open-network-stream (name buffer host service &rest parameters)
:end-of-command specifies a regexp matching the end of a command.
+:end-of-capability specifies a regexp matching the end of the
+ response to the command specified for :capability-command.
+ It defaults to the regexp specified for :end-of-command.
+
:success specifies a regexp matching a message indicating a
successful STARTTLS negotiation. For instance, the default
should be \"^3\" for an NNTP connection.
capability command, and should return the command to switch on
STARTTLS if the server supports STARTTLS, and nil otherwise.
+:always-query-capabilities says whether to query the server for
+ capabilities, even if we're doing a `plain' network connection.
+
+:client-certificate should either be a list where the first
+ element is the certificate key file name, and the second
+ element is the certificate file name itself, or `t', which
+ means that `auth-source' will be queried for the key and the
+ certificate. This parameter will only be used when doing TLS
+ or STARTTLS connections.
+
+:use-starttls-if-possible is a boolean that says to do opportunistic
+STARTTLS upgrades even if Emacs doesn't have built-in TLS functionality.
+
+:nogreeting is a boolean that can be used to inhibit waiting for
+a greeting from the server.
+
:nowait is a boolean that says the connection should be made
-asynchronously, if possible."
+ asynchronously, if possible."
(unless (featurep 'make-network-process)
(error "Emacs was compiled without networking support"))
(let ((type (plist-get parameters :type))
:nowait (plist-get parameters :nowait))
(let ((work-buffer (or buffer
(generate-new-buffer " *stream buffer*")))
- (fun (cond ((eq type 'plain) 'network-stream-open-plain)
- ((memq type '(nil network starttls))
+ (fun (cond ((and (eq type 'plain)
+ (not (plist-get parameters
+ :always-query-capabilities)))
+ 'network-stream-open-plain)
+ ((memq type '(nil network starttls plain))
'network-stream-open-starttls)
((memq type '(tls ssl)) 'network-stream-open-tls)
((eq type 'shell) 'network-stream-open-shell)
(list (car result)
:greeting (nth 1 result)
:capabilities (nth 2 result)
- :type (nth 3 result))
+ :type (nth 3 result)
+ :error (nth 4 result))
(car result))))))
+(defun network-stream-certificate (host service parameters)
+ (let ((spec (plist-get :client-certificate parameters)))
+ (cond
+ ((listp spec)
+ ;; Either nil or a list with a key/certificate pair.
+ spec)
+ ((eq spec t)
+ (let* ((auth-info
+ (car (auth-source-search :max 1
+ :host host
+ :port service)))
+ (key (plist-get auth-info :key))
+ (cert (plist-get auth-info :cert)))
+ (and key cert
+ (list key cert)))))))
+
;;;###autoload
(defalias 'open-protocol-stream 'open-network-stream)
(success-string (plist-get parameters :success))
(capability-command (plist-get parameters :capability-command))
(eoc (plist-get parameters :end-of-command))
+ (eo-capa (or (plist-get parameters :end-of-capability)
+ eoc))
;; Return (STREAM GREETING CAPABILITIES RESULTING-TYPE)
(stream (make-network-process :name name :buffer buffer
:host host :service service))
- (greeting (network-stream-get-response stream start eoc))
- (capabilities (network-stream-command stream capability-command eoc))
+ (greeting (and (not (plist-get parameters :nogreeting))
+ (network-stream-get-response stream start eoc)))
+ (capabilities (network-stream-command stream capability-command
+ eo-capa))
(resulting-type 'plain)
- starttls-command)
-
+ (builtin-starttls (and (fboundp 'gnutls-available-p)
+ (gnutls-available-p)))
+ starttls-available starttls-command error)
+
+ ;; First check whether the server supports STARTTLS at all.
+ (when (and capabilities success-string starttls-function)
+ (setq starttls-command
+ (funcall starttls-function capabilities)))
;; If we have built-in STARTTLS support, try to upgrade the
;; connection.
- (when (and (or (fboundp 'open-gnutls-stream)
- (and require-tls
- (executable-find "gnutls-cli")))
- capabilities success-string starttls-function
- (setq starttls-command
- (funcall starttls-function capabilities)))
+ (when (and starttls-command
+ (setq starttls-available
+ (or builtin-starttls
+ (and (or require-tls
+ (plist-get parameters :use-starttls-if-possible))
+ (starttls-available-p))))
+ (not (eq (plist-get parameters :type) 'plain)))
;; If using external STARTTLS, drop this connection and start
;; anew with `starttls-open-stream'.
- (unless (fboundp 'open-gnutls-stream)
+ (unless builtin-starttls
(delete-process stream)
(setq start (with-current-buffer buffer (point-max)))
- (let* ((starttls-use-gnutls t)
- (starttls-extra-arguments
- (if require-tls
+ (let* ((starttls-extra-arguments
+ (if (or require-tls
+ (member "--insecure" starttls-extra-arguments))
starttls-extra-arguments
;; For opportunistic TLS upgrades, we don't really
;; care about the identity of the peer.
- (cons "--insecure" starttls-extra-arguments))))
+ (cons "--insecure" starttls-extra-arguments)))
+ (starttls-extra-args starttls-extra-args)
+ (cert (network-stream-certificate host service parameters)))
+ ;; There are client certificates requested, so add them to
+ ;; the command line.
+ (when cert
+ (setq starttls-extra-arguments
+ (nconc (list "--x509keyfile" (expand-file-name (nth 0 cert))
+ "--x509certfile" (expand-file-name (nth 1 cert)))
+ starttls-extra-arguments)
+ starttls-extra-args
+ (nconc (list "--key-file" (expand-file-name (nth 0 cert))
+ "--cert-file" (expand-file-name (nth 1 cert)))
+ starttls-extra-args)))
(setq stream (starttls-open-stream name buffer host service)))
- (network-stream-get-response stream start eoc))
- (when (string-match success-string
- (network-stream-command stream starttls-command eoc))
+ (network-stream-get-response stream start eoc)
+ ;; Requery capabilities for protocols that require it; i.e.,
+ ;; EHLO for SMTP.
+ (when (plist-get parameters :always-query-capabilities)
+ (network-stream-command stream capability-command eo-capa)))
+ (when (let ((response
+ (network-stream-command stream starttls-command eoc)))
+ (and response (string-match success-string response)))
;; The server said it was OK to begin STARTTLS negotiations.
- (if (fboundp 'open-gnutls-stream)
- (gnutls-negotiate :process stream :hostname host)
+ (if builtin-starttls
+ (let ((cert (network-stream-certificate host service parameters)))
+ (condition-case nil
+ (gnutls-negotiate :process stream :hostname host
+ :keylist (and cert (list cert)))
+ ;; If we get a gnutls-specific error (for instance if
+ ;; the certificate the server gives us is completely
+ ;; syntactically invalid), then close the connection
+ ;; and possibly (further down) try to create a
+ ;; non-encrypted connection.
+ (gnutls-error
+ (delete-process stream))))
(unless (starttls-negotiate stream)
(delete-process stream)))
(if (memq (process-status stream) '(open run))
(network-stream-get-response stream start eoc)))
;; Re-get the capabilities, which may have now changed.
(setq capabilities
- (network-stream-command stream capability-command eoc))))
+ (network-stream-command stream capability-command eo-capa))))
;; If TLS is mandatory, close the connection if it's unencrypted.
- (and require-tls
- (eq resulting-type 'plain)
- (delete-process stream))
+ (when (and require-tls
+ ;; ... but Emacs wasn't able to -- either no built-in
+ ;; support, or no gnutls-cli installed.
+ (eq resulting-type 'plain))
+ (setq error
+ (if (or (null starttls-command)
+ starttls-available)
+ "Server does not support TLS"
+ ;; See `starttls-available-p'. If this predicate
+ ;; changes to allow running under Windows, the error
+ ;; message below should be amended.
+ (if (memq system-type '(windows-nt ms-dos))
+ (concat "Emacs does not support TLS")
+ (concat "Emacs does not support TLS, and no external `"
+ (if starttls-use-gnutls
+ starttls-gnutls-program
+ starttls-program)
+ "' program was found"))))
+ (delete-process stream)
+ (setq stream nil))
;; Return value:
- (list stream greeting capabilities resulting-type)))
+ (list stream greeting capabilities resulting-type error)))
(defun network-stream-command (stream command eoc)
(when command
(defun network-stream-open-tls (name buffer host service parameters)
(with-current-buffer buffer
(let* ((start (point-max))
- (use-builtin-gnutls (fboundp 'open-gnutls-stream))
+ (use-builtin-gnutls (and (fboundp 'gnutls-available-p)
+ (gnutls-available-p)))
(stream
(funcall (if use-builtin-gnutls
'open-gnutls-stream
(list nil nil nil 'plain)
;; If we're using tls.el, we have to delete the output from
;; openssl/gnutls-cli.
- (when (and (null use-builtin-gnutls) eoc)
+ (when (and (null use-builtin-gnutls)
+ eoc)
(network-stream-get-response stream start eoc)
(goto-char (point-min))
(when (re-search-forward eoc nil t)
?p service))))))
(list stream
(network-stream-get-response stream start eoc)
- (network-stream-command stream capability-command eoc)
+ (network-stream-command stream capability-command
+ (or (plist-get parameters :end-of-capability)
+ eoc))
'plain)))
(provide 'network-stream)