778ab48e9012e458f4298b52d55bdfbedad58f1e
[elisp/starttls.git] / starttls.c
1 /* TLSv1 filter for STARTTLS extension.
2
3    Copyright (C) 1999, 2000 Daiki Ueno <ueno@unixuser.org>
4
5    Author: Daiki Ueno <ueno@unixuser.org>
6         Kenichi OKADA <okada@opaopa.org>
7    Created: 1999-11-19
8    Keywords: TLS, OpenSSL
9
10    This file is not part of any package.
11
12    This program is free software; you can redistribute it and/or modify 
13    it under the terms of the GNU General Public License as published by 
14    the Free Software Foundation; either version 2, or (at your option)  
15    any later version.                                                   
16
17    This program is distributed in the hope that it will be useful,      
18    but WITHOUT ANY WARRANTY; without even the implied warranty of       
19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        
20    GNU General Public License for more details.                         
21
22    You should have received a copy of the GNU General Public License    
23    along with GNU Emacs; see the file COPYING.  If not, write to the    
24    Free Software Foundation, Inc., 59 Temple Place - Suite 330,         
25    Boston, MA 02111-1307, USA.                                          
26
27 */
28
29 /*
30   How to compile: (OpenSSL is required)
31   
32   gcc -I/usr/local/ssl/include -o starttls starttls.c \
33     -L/usr/local/ssl/lib -lssl -lcrypto
34
35 */
36
37 #include <sys/types.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41
42 #include <unistd.h>
43
44 /* OpenSSL library. */
45
46 #include <openssl/lhash.h>
47 #include <openssl/bn.h>
48 #include <openssl/err.h>
49 #include <openssl/pem.h>
50 #include <openssl/x509.h>
51 #include <openssl/ssl.h>
52
53 #ifdef HAVE_SOCKS_H
54 #include <socks.h>
55 #endif 
56
57 #ifndef HAVE_GETADDRINFO
58 #include "getaddrinfo.h"
59 #endif /* !HAVE_GETADDRINFO */
60
61 #include <sys/time.h>
62 #include <sys/socket.h>
63 #include <sys/file.h>
64 #include <sys/ioctl.h>
65 #include <sys/stat.h>
66 #include <netdb.h>
67 #include <stdio.h>
68 #include <signal.h>
69 #include <fcntl.h>
70 #include <netinet/in.h>
71 #define _GNU_SOURCE
72 #include <getopt.h>
73
74 #ifdef HAVE_BASENAME
75 # ifdef HAVE_LIBGEN_H
76 #  include <libgen.h>
77 #  ifdef basename
78 #   undef basename
79 #  endif
80 # endif
81 # include <string.h>
82 #else
83 inline char *
84 basename(path) 
85      const char *path;
86
87   char *p = rindex((path), '/');
88   return p ? p + 1 : (path);
89 }
90 #endif
91
92 #define true 1
93
94 static SSL_CTX *tls_ctx = NULL;
95 static SSL *tls_conn = NULL;
96 static int tls_fd;
97
98 static char *opt_cert_file = NULL, *opt_key_file = NULL;
99 static int  opt_verify = 0;
100 static int opt_force;
101
102 static int
103 tls_ssl_ctx_new (cert_file, key_file)
104   const char *cert_file, *key_file;
105 {
106   SSL_load_error_strings ();
107   SSLeay_add_ssl_algorithms ();
108
109   tls_ctx = SSL_CTX_new (TLSv1_client_method());
110   if (!tls_ctx)
111     return -1;
112
113   SSL_CTX_set_options (tls_ctx, SSL_OP_ALL /* Work around all known bugs */); 
114
115   if (cert_file)
116     {
117       if (SSL_CTX_use_certificate_file (tls_ctx, cert_file, 
118                                         SSL_FILETYPE_PEM) <= 0)
119         return -1;
120       if (!key_file)
121         key_file = cert_file;
122       if (SSL_CTX_use_PrivateKey_file (tls_ctx, key_file, 
123                                        SSL_FILETYPE_PEM) <= 0)
124         return -1;
125       if (!SSL_CTX_check_private_key (tls_ctx))
126         return -1;
127     }
128
129   SSL_CTX_set_verify (tls_ctx, SSL_VERIFY_NONE, NULL);
130
131   return 0;
132 }
133
134 static int
135 tls_ssl_new(ctx, s)
136   SSL_CTX *ctx;
137   int s;
138 {
139   SSL_SESSION *session;
140   SSL_CIPHER *cipher;
141   X509   *peer;
142
143   tls_conn = (SSL *) SSL_new (ctx);
144   if (!tls_conn)
145     return -1;
146   SSL_clear(tls_conn);
147
148   if (!SSL_set_fd (tls_conn, s))
149     return -1;
150
151   SSL_set_connect_state (tls_conn);
152
153   if (SSL_connect (tls_conn) <= 0)
154     {
155       session = SSL_get_session (tls_conn);
156       if (session)
157         SSL_CTX_remove_session (ctx, session);
158       if (tls_conn!=NULL)
159         SSL_free (tls_conn);
160       return -1;
161     }
162
163   return 0;
164 }
165
166 static int
167 tls_connect (hostname, service)
168      const char *hostname, *service;
169 {
170   struct protoent *proto;
171   struct addrinfo *in, hints;
172   int server, false = 0;
173
174   proto = getprotobyname ("tcp");
175   if (!proto)
176     return -1;
177
178   memset (&hints, 0, sizeof (hints));
179   hints.ai_family = AF_UNSPEC;
180   hints.ai_socktype = SOCK_STREAM;
181   hints.ai_protocol = proto->p_proto;
182
183   if (getaddrinfo (hostname, service, &hints, &in) < 0) 
184     return -1;
185
186   server = socket (in->ai_family, in->ai_socktype, 0);
187   if (server < 0)
188     return -1;
189
190   if (setsockopt (server, SOL_SOCKET, SO_KEEPALIVE,
191                   (const char *) &false, sizeof (false))) 
192     return -1;
193
194   if (connect (server, in->ai_addr, in->ai_addrlen) < 0)
195     {
196       close (server);
197       return -1;
198     }
199
200   return server;
201 }
202
203 static void
204 tls_negotiate (sig)
205      int sig;
206 {
207   if (tls_ssl_ctx_new (opt_cert_file, opt_key_file) == -1)
208     return;
209
210   (void) tls_ssl_new (tls_ctx, tls_fd); /* Negotiation has done. */
211 }
212
213 static void
214 usage (progname)
215      const char *progname;
216 {
217   printf ("%s (%s) %s\n"
218           "Copyright (C) 1999 Free Software Foundation, Inc.\n"
219           "This program comes with ABSOLUTELY NO WARRANTY.\n"
220           "This is free software, and you are welcome to redistribute it\n"
221           "under certain conditions. See the file COPYING for details.\n\n"
222           "Usage: %s [options] host port\n\n"
223           "Options:\n\n"
224           " --cert-file [file]      specify certificate file\n"
225           " --key-file [file]       specify private key file\n"
226           " --verify [level]        set verification level\n"
227           " --force                 force negotiate\n",
228           progname, PACKAGE, VERSION, progname);
229 }
230      
231 int
232 main (argc, argv) 
233   int argc;
234   char **argv;
235 {
236   int in = fileno (stdin), out = fileno (stdout), 
237     nbuffer, wrote;
238   fd_set readfds, writefds;
239   char buffer[BUFSIZ], *retry;
240   struct sigaction act;
241
242   int this_option_optind = optind ? optind : 1;
243   int option_index = 0, c;
244   static struct option long_options[] =
245     {
246       {"cert-file", 1, 0, 'c'},
247       {"key-file", 1, 0, 'k'},
248       {"verify", 1, 0, 'v'},
249       {"force", 0, 0, 'f'},
250       {0, 0, 0, 0}
251     };
252
253   while (1)
254     {
255       c = getopt_long (argc, argv, "c:k:v:f", long_options, &option_index);
256       if (c == -1)
257         break;
258     
259       switch (c)
260         {
261         case 'c':
262           opt_cert_file = optarg;
263           break;
264         case 'k':
265           opt_key_file = optarg;
266           break;
267         case 'v':
268           opt_verify = atoi (optarg);
269           break;
270         case 'f':
271           opt_force = true;
272           break;
273         default:
274           usage (basename (argv[0]));
275           return 1;
276         }
277     }
278
279   if (optind+2 != argc)
280     {
281       usage (basename (argv[0]));
282       return 1;
283     }
284
285   tls_fd = tls_connect (argv[optind], argv[optind+1]);
286   if (tls_fd < 0)
287     {
288       perror ("tls_connect");
289       return 1;
290     }
291
292   memset (&act, 0, sizeof (act));
293   act.sa_handler = tls_negotiate;
294   sigemptyset (&act.sa_mask);
295   act.sa_flags = SA_RESTART|SA_RESETHAND;
296   sigaction (SIGALRM, &act, NULL);
297
298   if (opt_force == true)
299     tls_negotiate();
300
301   while (1)
302     {
303       FD_SET (tls_fd, &readfds);
304       FD_SET (in, &readfds);
305       if (select (tls_fd+1, &readfds, NULL, NULL, NULL) == -1
306           && errno != EINTR )
307         {
308           perror ("select");
309           return 1;
310         }
311       if (FD_ISSET (in, &readfds))
312         {
313           nbuffer = read (in, buffer, BUFSIZ/2);
314
315           if (nbuffer == 0)
316             goto finish;
317           for (retry = buffer; nbuffer > 0; nbuffer -= wrote, retry += wrote)
318             {
319               FD_SET (tls_fd, &writefds);
320               if (select (tls_fd+1, NULL, &writefds, NULL, NULL) == -1)
321                 {
322                   perror ("select");
323                   return 1;
324                 }
325               if (tls_conn) 
326                 wrote = SSL_write (tls_conn, retry, nbuffer);
327               else
328                 wrote = write (tls_fd, retry, nbuffer);
329               if (wrote < 0) goto finish;
330             }
331         }
332       if (FD_ISSET (tls_fd, &readfds))
333         {
334           if (tls_conn)
335             nbuffer = SSL_read (tls_conn, buffer, BUFSIZ/8);
336           else
337             nbuffer = read (tls_fd, buffer, BUFSIZ/2);
338           if (nbuffer == 0)
339             goto finish;
340           for (retry = buffer; nbuffer > 0; nbuffer -= wrote, retry += wrote)
341             {
342               FD_SET (out, &writefds);
343               if (select (out+1, NULL, &writefds, NULL, NULL) == -1)
344                 {
345                   perror ("select");
346                   return 1;
347                 }
348               wrote = write (out, retry, nbuffer);
349               if (wrote < 0) goto finish;
350             }
351         }
352     }
353
354  finish:
355   close (in);
356   close (out);
357   
358   return 0;
359 }