1 ;;; proto-stream.el --- negotiating TLS, STARTTLS and other connections
2 ;; Copyright (C) 2010 Free Software Foundation, Inc.
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;; This file is part of GNU Emacs.
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 3, or (at your option)
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING. If not, write to the
21 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 ;; Boston, MA 02110-1301, USA.
26 ;; This library is meant to provide the glue between modules that want
27 ;; to establish a network connection to a server for protocols such as
28 ;; IMAP, NNTP, SMTP and POP3.
30 ;; The main problem is that there's more than a couple of interfaces
31 ;; towards doing this. You have normal, plain connections, which are
32 ;; no trouble at all, but you also have TLS/SSL connections, and you
33 ;; have STARTTLS. Negotiating this for each protocol can be rather
34 ;; tedious, so this library provides a single entry point, and hides
35 ;; much of the ugliness.
43 (require 'format-spec)
46 (defun open-proto-stream (name buffer host service &rest parameters)
47 "Open a network stream to HOST.
48 The first four parameters have the same meaning as in
49 `open-network-stream'. The PARAMETERS is a keyword list that can
50 have the following values:
52 :type -- either `network', `tls', `shell' or `starttls'. If
53 omitted, the default is `network'.
55 :capability-command -- a function that takes a stream parameter"
56 (let ((type (or (cadr (memq :type parameters)) 'stream)))
57 (when (and (eq type 'starttls)
58 (fboundp 'open-gnutls-stream))
62 (destructuring-bind (stream greeting capabilities)
63 (funcall (intern (format "proto-stream-open-%s" type) obarray)
64 name buffer host service parameters)
66 (memq (process-status stream)
69 greeting capabilities))))
71 (defun proto-stream-open-network (name buffer host service parameters)
72 (let ((start (with-current-buffer buffer (point)))
73 (stream (open-network-stream name buffer host service))
74 (capability-command (cadr (memq :capability-command parameters)))
75 (greeting (proto-stream-get-response stream start)))
76 (if (not capability-command)
77 (list stream greeting nil)
79 (proto-stream-capabilities stream capability-command))
81 (funcall (cadr (memq :starttls-function parameters))
82 stream capabilities)))
84 ((not starttls-command)
85 ;; If this server doesn't support STARTTLS, but we have
86 ;; requested it explicitly, then close the connection and
88 (if (eq (cadr (memq :type parameters)) 'starttls)
90 (delete-process stream)
92 ;; Otherwise, just return this plain network connection.
93 (list stream greeting capabilities)))
94 ((fboundp 'open-gnutls-stream)
95 (setq start (with-current-buffer buffer (point)))
96 (process-send-string stream starttls-command)
97 (proto-stream-get-response stream start)
98 (gnutls-negotiate stream nil)
99 ;; Re-get the capabilities, since they may have changed
100 ;; after switching to TLS.
101 (setq start (with-current-buffer buffer (point)))
102 (process-send-string stream capability-command)
103 (list stream greeting (proto-stream-get-response stream start)))
105 (delete-process stream)
106 (proto-stream-open-starttls name buffer host service parameters)))))))
108 (defun proto-stream-capabilities (stream command)
109 (let ((start (with-current-buffer buffer (point))))
110 (process-send-string stream command)
111 (proto-stream-get-response stream start)))
113 (defun proto-stream-open-starttls (name buffer host service parameters)
114 (proto-stream-capability-open
115 (with-current-buffer buffer (point))
116 (starttls-open-stream name buffer host service)
119 (defun proto-stream-get-response (stream start)
120 (with-current-buffer (process-buffer stream)
123 (while (and (memq (process-status stream)
125 (not (search-forward "\n" nil t)))
126 (accept-process-output stream 0 50)
128 (if (= start (point))
129 ;; The process died; return nil.
131 ;; Return the data we got back.
132 (buffer-substring start (point))))))
134 (defun proto-stream-open-tls (name buffer host service parameters)
135 (proto-stream-capability-open
136 (with-current-buffer buffer (point))
137 (funcall (if (fboundp 'open-gnutls-stream)
140 name buffer host service)
143 (defun proto-stream-open-shell (name buffer host service parameters)
144 (proto-stream-capability-open
145 (with-current-buffer buffer (point))
146 (let ((process-connection-type nil))
147 (start-process name buffer shell-file-name
150 (cadr (memq :shell-command parameters))
156 (defun proto-stream-capability-open (start stream parameters)
157 (let ((capability-command (cadr (memq :capability-command parameters)))
158 (greeting (proto-stream-get-response stream start)))
159 (list stream greeting
160 (and capability-command
161 (proto-stream-capabilities stream capability-command)))))
163 (provide 'proto-stream)
165 ;;; proto-stream.el ends here