Mercurial > hg > xemacs-beta
diff lisp/code-process.el @ 5814:a216b3c2b09e
Add TLS support. See xemacs-patches message with ID
<CAHCOHQk6FNm2xf=XiGEpPq43+7WOzNZ=SuD9V79o3wb9WVCTrQ@mail.gmail.com>.
author | Jerry James <james@xemacs.org> |
---|---|
date | Tue, 07 Oct 2014 21:16:10 -0600 |
parents | 91b3aa59f49b |
children |
line wrap: on
line diff
--- a/lisp/code-process.el Thu Oct 02 10:19:00 2014 +0200 +++ b/lisp/code-process.el Tue Oct 07 21:16:10 2014 -0600 @@ -271,9 +271,169 @@ See also the function `find-operation-coding-system'.") -(defun open-network-stream (name buffer host service &optional protocol) +(defun network-stream-get-response (stream start end-of-command) + (when end-of-command + (with-current-buffer (process-buffer stream) + (save-excursion + (goto-char start) + (while (and (memq (process-status stream) '(open run)) + (not (re-search-forward end-of-command nil t))) + (accept-process-output stream 0 50) + (goto-char start)) + ;; Return the data we got back, or nil if the process died. + (unless (= start (point)) + (buffer-substring start (point))))))) + +(defun network-stream-command (stream command eoc) + (when command + (let ((start (point-max (process-buffer stream)))) + (process-send-string stream command) + (network-stream-get-response stream start eoc)))) + +(defun network-stream-open-plain (name buffer host service parameters) + (let ((start (point buffer)) + (stream + (open-network-stream-internal name buffer host service + (plist-get parameters :protocol)))) + (list stream + (network-stream-get-response stream start + (plist-get parameters :end-of-command)) + nil + 'plain))) + +(defun network-stream-open-tls (name buffer host service parameters) + (with-current-buffer buffer + (let* ((start (point-max)) + (stream + (open-network-stream-internal name buffer host service + (plist-get parameters :protocol) t))) + (if (null stream) + (list nil nil nil 'plain) + (let ((eoc (plist-get parameters :end-of-command)) + (capability-command (plist-get parameters :capability-command))) + (list stream + (network-stream-get-response stream start eoc) + (network-stream-command stream capability-command eoc) + 'tls)))))) + +(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) + (when (fboundp 'auth-source-search) + (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)))))))) + +(defun network-stream-open-starttls (name buffer host service parameters) + (let* ((start (point buffer)) + (require-tls (eq (plist-get parameters :type) 'starttls)) + (starttls-function (plist-get parameters :starttls-function)) + (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)) + (protocol (plist-get parameters :protocol)) + ;; Return (STREAM GREETING CAPABILITIES RESULTING-TYPE) + (stream (open-network-stream-internal name buffer host service + protocol)) + (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-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 starttls-command + (setq starttls-available t) + (not (eq (plist-get parameters :type) 'plain))) + (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. + (let ((cert (network-stream-certificate host service parameters))) + (condition-case nil + (tls-negotiate stream host (and cert (list cert))) + ;; If we get a tls-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)))) + (if (memq (process-status stream) '(open run)) + (setq resulting-type 'tls) + ;; We didn't successfully negotiate STARTTLS; if TLS + ;; isn't demanded, reopen an unencrypted connection. + (unless require-tls + (setq stream + (make-network-process :name name :buffer buffer + :host host :service service)) + (network-stream-get-response stream start eoc))) + ;; Re-get the capabilities, which may have now changed. + (setq capabilities + (network-stream-command stream capability-command eo-capa)))) + + ;; If TLS is mandatory, close the connection if it's unencrypted. + (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 error))) + +;; Requires that format-spec.el from gnus be loaded +(defun network-stream-open-shell (name buffer host service parameters) + (require 'format-spec) + (let* ((capability-command (plist-get parameters :capability-command)) + (eo-capa (plist-get parameters :end-of-capability)) + (eoc (plist-get parameters :end-of-command)) + (start (point buffer)) + (stream (let ((process-connection-type nil)) + (start-process name buffer shell-file-name + shell-command-switch + (format-spec + (plist-get parameters :shell-command) + (format-spec-make + ?s host + ?p service)))))) + (list stream + (network-stream-get-response stream start eoc) + (network-stream-command stream capability-command (or eo-capa eoc)) + 'plain))) + +(defun open-network-stream (name buffer host service &rest parameters) "Open a TCP connection for a service to a host. -Return a process object to represent the connection. +Normally, return a process object to represent the connection. If the +:return-list parameter is non-NIL, instead return a list; see below. Input and output work as for subprocesses; `delete-process' closes it. NAME is name for process. It is modified if necessary to make it unique. BUFFER is the buffer (or buffer-name) to associate with the process. @@ -284,9 +444,64 @@ Third arg is name of the host to connect to, or its IP address. Fourth arg SERVICE is name of the service desired, or an integer specifying a port number to connect to. -Fifth argument PROTOCOL is a network protocol. Currently 'tcp - (Transmission Control Protocol) and 'udp (User Datagram Protocol) are - supported. When omitted, 'tcp is assumed. + +The remaining PARAMETERS should be a sequence of keywords and values: +- :protocol is a network protocol. Currently 'tcp (Transmission Control + Protocol) and 'udp (User Datagram Protocol) are supported. When + omitted, 'tcp is assumed. +- :type specifies the connection type; it is one of the following: + nil or `network': begin with an ordinary network connection, and if + the parameters :success and :capability-command are also + supplied, try to upgrade to an encrypted connection via + STARTTLS. If that fails (e.g., HOST does not support TLS), + retain an unencrypted connection. + `plain': an ordinary, unencrypted network connection. + `starttls': begin with an ordinary network connection and try to + upgrade via STARTTLS. If that fails, drop the connection + and return a killed process object. + `tls': a TLS connection. + `ssl': a synonym for `tls'. + `shell': a shell connection. +- :return-list specifies this function's return value. + If omitted or nil, return a process object as usual. Otherwise, return + (PROC . PROPS), where PROC is a process object and PROPS is a plist of + connection properties, with these keywords: + :greeting: the greeting returned by HOST (a string), or nil. + :capabilities: a string representing HOST's capabilities, or nil if none + could be found. + :type: the resulting connection type, `plain' (unencrypted) or `tls' + (encrypted). +- :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 example, the default should be \"^3\" for an + NNTP connection. +- :capability-command specifies a command used to query HOST for its + capabilities. For example, this should be \"1 CAPABILITY\\r\\n\" for + IMAP. +- :starttls-function specifies a function for handling STARTTLS. This + function should take one parameter, the response to the capability + command, and should return the command to switch on STARTTLS if the + server supports it, or nil otherwise. +- :always-query-capabilities, if non-nil, indicates that the server should + be queried for capabilities even if constructing a `plain' network + connection. +- :client-certificate is either a list (certificate-key-filename + certificate-filename), or `t', meaning that `auth-source' will be + queried for the key and certificate. This parameter is used only when + constructing a TLS or STARTTLS connection. +- :use-starttls-if-possible, if non-nil, indicates that STARTTLS should + be used even if TLS support is not compiled in to XEmacs. +- :nogreeting, if non-nil, indicates that we should not wait for a + greeting from the server. +- :nowait, if non-nil, indicates that an asynchronous connection should be + made, if possible. NOTE: this is currently unimplemented. + +For backwards compatibility, if exactly five arguments are given, the fifth +must be one of nil, 'tcp, or 'udp. Both nil and 'tcp select TCP (Transmission +Control Protocol) and 'udp selects UDP (User Datagram Protocol). Output via `process-send-string' and input via buffer or filter (see `set-process-filter') are stream-oriented. That means UDP datagrams are @@ -315,6 +530,8 @@ You can change the coding systems later on using `set-process-coding-system', `set-process-input-coding-system', or `set-process-output-coding-system'." + (when (and (car parameters) (not (cdr parameters))) + (setq parameters (list :protocol (car parameters)))) (let (cs-r cs-w) (let (ret) (catch 'found @@ -351,8 +568,44 @@ (coding-system-for-write (or coding-system-for-write cs-w (cdr default-network-coding-system) - 'raw-text))) - (open-network-stream-internal name buffer host service protocol)))) + 'raw-text)) + (type (plist-get parameters :type)) + (return-list (plist-get parameters :return-list)) + (capability-command (plist-get parameters :capability-command))) + (if (and (not return-list) + (or (eq type 'plain) + (and (or (null type) (eq type 'network)) + (not (and (plist-get parameters :success) + capability-command))))) + ;; The simplest case: a plain connection + (open-network-stream-internal name buffer host service + (plist-get parameters :protocol)) + (let ((work-buffer (or buffer + (generate-new-buffer " *stream buffer*"))) + (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) + (t (error "Invalid connection type" type)))) + result) + (unwind-protect + (setq result + (funcall fun name work-buffer host service parameters)) + (unless buffer + (and (processp (car result)) + (set-process-buffer (car result) nil)) + (kill-buffer work-buffer))) + (if return-list + (list (car result) + :greeting (nth 1 result) + :capabilities (nth 2 result) + :type (nth 3 result) + :error (nth 4 result)) + (car result))))))) (defun set-buffer-process-coding-system (decoding encoding) "Set coding systems for the process associated with the current buffer.