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