Rewrite.
[gnus] / contrib / starttls.el
1 ;;; starttls.el --- STARTTLS support via wrapper around GNU TLS
2
3 ;; Copyright (C) 2003 Free Software Foundation, Inc.
4
5 ;; Author: Simon Josefsson <simon@josefsson.org>
6 ;; Keywords: comm, tls, gnutls, ssl
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;; This package implements a simple wrapper around the GNU TLS command
28 ;; line application "gnutls-cli" to make Emacs support STARTTLS.
29 ;;
30 ;; Usage is similar to `open-network-stream', i.e.:
31 ;;
32 ;; (setq tmp (open-starttls-stream "test" (current-buffer) "cyrus.andrew.cmu.edu" 143))
33 ;; #<process test<9>>
34 ;; (process-send-string tmp ". starttls\n")
35 ;; nil
36 ;; (negotiate-starttls tmp)
37 ;; "*** Starting TLS handshake
38 ;; - Certificate type: X.509
39 ;;  - Certificate info:
40 ;;  # Certificate is valid since: Thu Jun 26 19:00:00 CEST 2003
41 ;;  # Certificate expires: Sat Jun 26 19:00:00 CEST 2004
42 ;;  # Certificate fingerprint: 8d 59 d6 e1 c9 91 dc 5a bb 38 47 8c ec 85 1b 99
43 ;;  # Certificate serial number: 3e fb 52 ce
44 ;;  # Certificate version: #3
45 ;;  # Certificate public key algorithm: RSA
46 ;;  #   Modulus: 1024 bits
47 ;;  # CN=cyrus.andrew.cmu.edu,OU=Computing Services,O=Carnegie Mellon University,L=Pittsburgh,ST=Pennsylvania,C=US
48 ;;  # Certificate Issuer's info:
49 ;;  # CN=CMU CA mail 1,OU=Computing Services,O=Carnegie Mellon University,L=Pittsburgh,ST=Pennsylvania,C=US
50 ;;
51 ;; - Peer's certificate is NOT trusted
52 ;; - Version: TLS 1.0
53 ;; - Key Exchange: RSA
54 ;; - Cipher: ARCFOUR 128
55 ;; - MAC: SHA
56 ;; - Compression: NULL
57 ;; "
58 ;; (process-send-string tmp ". capability\n")
59 ;; nil
60 ;; (process-send-string tmp ". logout\n")
61 ;; nil
62 ;;
63 ;; Resolving 'cyrus.andrew.cmu.edu'...
64 ;; Connecting to '128.2.10.174:143'...
65 ;;
66 ;; - Simple Client Mode:
67 ;;
68 ;; * OK mail-fe4.andrew.cmu.edu Cyrus IMAP4 Murder v2.1.15-077 server ready\r
69 ;; . OK Begin TLS negotiation now\r
70 ;; * CAPABILITY IMAP4 IMAP4rev1 ACL QUOTA LITERAL+ MAILBOX-REFERRALS NAMESPACE UIDPLUS ID NO_ATOMIC_RENAME UNSELECT CHILDREN MULTIAPPEND SORT THREAD=ORDEREDSUBJECT THREAD=REFERENCES AUTH=PLAIN AUTH=KERBEROS_V4 AUTH=GSSAPI AUTH=ANONYMOUS ANNOTATEMORE\r
71 ;; . OK Completed\r
72 ;; * BYE LOGOUT received\r
73 ;; . OK Completed\r
74 ;; *** Received corrupted data(-9) - server has terminated the connection abnormally
75 ;;
76 ;; Process test<9> finished
77
78 ;;; Code:
79
80 (eval-and-compile
81   (autoload 'format-spec "format-spec")
82   (autoload 'format-spec-make "format-spec"))
83
84 (defgroup starttls nil
85   "Negotiated Transport Layer Security (STARTTLS) parameters."
86   :group 'comm)
87
88 (defcustom starttls-program "gnutls-cli"
89   "The program to run in a subprocess to open an STARTTLS connection.
90 The program should read input on stdin and write output to
91 stdout.  Also see `starttls-connect' and `starttls-success' for
92 what the program should output after initial connection and
93 successful negotiation respectively."
94   :type 'string
95   :group 'starttls)
96
97 (defcustom starttls-extra-args nil
98   "List of extra arguments to `starttls-program'.
99 E.g., (\"--protocols\" \"ssl3\")."
100   :type '(repeat string)
101   :group 'starttls)
102
103 (defcustom starttls-process-connection-type nil
104   "*Value for `process-connection-type' to use when starting STARTTLS process."
105   :type 'boolean
106   :group 'starttls)
107
108 (defcustom starttls-connect "- Simple Client Mode:\n\n"
109   "*Regular expression indicating successful connection.
110 The default is what GNUTLS's \"gnutls-cli\" outputs."
111   ;; cli.c:main() print this string when it is starting to run in the
112   ;; application read/write phase.  If the logic, or the string
113   ;; itself, is modified, this have to be updated.
114   :type 'regexp
115   :group 'starttls)
116
117 (defcustom starttls-failure "*** Handshake has failed"
118   "*Regular expression indicating failed TLS handshake.
119 The default is what GNUTLS's \"gnutls-cli\" outputs."
120   ;; cli.c:do_handshake() print this string on failure.  If the logic,
121   ;; or the string itself, is modified, this have to be updated.
122   :type 'regexp
123   :group 'starttls)
124
125 (defcustom starttls-success "- Compression: "
126   "*Regular expression indicating completed TLS handshakes.
127 The default is what GNUTLS's \"gnutls-cli\" outputs."
128   ;; cli.c:do_handshake() calls, on success, common.c:print_info(),
129   ;; that unconditionally print this string last.  If that logic, or
130   ;; the string itself, is modified, this have to be updated.
131   :type 'regexp
132   :group 'starttls)
133
134 (defun negotiate-starttls (process)
135   "Negotiate TLS on process opened by `open-starttls-stream'.
136 This should typically only be done once.  It typically return a
137 multi-line informational message with information about the
138 handshake, or NIL on failure."
139   (let (buffer response old-max done-ok done-bad)
140     (if (null (setq buffer (process-buffer process)))
141         ;; XXX how to remove/extract the TLS negotiation junk?
142         (signal-process (process-id process) 'SIGALRM)
143       (with-current-buffer buffer
144         (save-excursion
145           (goto-char (point-max))
146           (setq old-max (point))
147           (signal-process (process-id process) 'SIGALRM)
148           (while (and process
149                       (memq (process-status process) '(open run))
150                       (save-excursion
151                         (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
152                         (goto-char old-max)
153                         (progn
154                           (setq
155                            done-ok (re-search-forward starttls-success nil t)
156                            done-bad (re-search-forward starttls-failure nil t))
157                           (not (or done-ok done-bad)))))
158             (accept-process-output process 1 100)
159             (sit-for 0.1))
160           (setq info (buffer-substring-no-properties old-max (point-max)))
161           (delete-region old-max (point-max))
162           (if (or (and done-ok (not done-bad))
163                   ;; prevent mitm that fake success msg after failure msg.
164                   (and done-ok done-bad (< done-ok done-bad)))
165               info
166             (message "STARTTLS negotiation failed: %s" info)
167             nil))))))
168
169 (defun open-starttls-stream (name buffer host service)
170   "Open a TLS connection for a service to a host.
171 Returns a subprocess-object to represent the connection.
172 Input and output work as for subprocesses; `delete-process' closes it.
173 Args are NAME BUFFER HOST SERVICE.
174 NAME is name for process.  It is modified if necessary to make it unique.
175 BUFFER is the buffer (or buffer-name) to associate with the process.
176  Process output goes at end of that buffer, unless you specify
177  an output stream or filter function to handle the output.
178 Third arg is name of the host to connect to, or its IP address.
179 Fourth arg SERVICE is name of the service desired, or an integer
180 specifying a port number to connect to."
181   (message "Opening STARTTLS connection to `%s'..." host)
182   (let* (done
183          (old-max (with-current-buffer buffer (point-max)))
184          (process-connection-type starttls-process-connection-type)
185          (process (apply #'start-process name buffer
186                          starttls-program "-s" host
187                          "-p" (if (integerp service)
188                                   (int-to-string service)
189                                 service)
190                          starttls-extra-args))
191          response)
192     (process-kill-without-query process)
193     (while (and process
194                 (memq (process-status process) '(open run))
195                 (save-excursion
196                   (set-buffer buffer) ;; XXX "blue moon" nntp.el bug
197                   (goto-char (point-min))
198                   (not (setq done (re-search-forward
199                                    starttls-connect nil t)))))
200       (accept-process-output process 0 100)
201       (sit-for 0.1))
202     (if done
203         (progn
204           (with-current-buffer buffer
205             (delete-region old-max done))
206           (setq done process))
207       (delete-process process))
208     (message "Opening STARTTLS connection to `%s'...%s"
209              host (if done "done" "failed"))
210     done))
211
212 ;; Compatibility with starttls.el by Daiki Ueno <ueno@unixuser.org>:
213 (defalias 'starttls-open-stream 'open-starttls-stream)
214 (defalias 'starttls-negotiate 'negotiate-starttls)
215
216 (provide 'starttls)
217
218 ;;; starttls.el ends here