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