2004-03-21 Kenichi Okada <okada@opaopa.org>
[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 #include <sys/time.h>
43 #include <sys/socket.h>
44 #include <sys/file.h>
45 #include <sys/ioctl.h>
46 #include <sys/stat.h>
47 #include <netdb.h>
48 #include <stdio.h>
49 #include <signal.h>
50 #include <fcntl.h>
51 #include <netinet/in.h>
52 #ifdef HAVE_POLL_H
53 #include <sys/poll.h>
54 #endif
55 #define _GNU_SOURCE
56 #include "getopt.h"
57
58 static SSL_CTX *tls_ctx = NULL;
59 static SSL *tls_conn = NULL;
60 static int tls_fd;
61
62 static char *opt_cert_file = NULL, *opt_key_file = NULL;
63 static int  opt_verify = 0;
64
65 static int
66 tls_ssl_ctx_new (cert_file, key_file)
67   const char *cert_file, *key_file;
68 {
69   SSL_load_error_strings ();
70   SSLeay_add_ssl_algorithms ();
71
72   tls_ctx = SSL_CTX_new (TLSv1_client_method());
73   if (!tls_ctx)
74     return -1;
75
76   SSL_CTX_set_options (tls_ctx, SSL_OP_ALL /* Work around all known bugs */); 
77
78   if (cert_file)
79     {
80       if (SSL_CTX_use_certificate_file (tls_ctx, cert_file, 
81                                         SSL_FILETYPE_PEM) <= 0)
82         return -1;
83       if (!key_file)
84         key_file = cert_file;
85       if (SSL_CTX_use_PrivateKey_file (tls_ctx, key_file, 
86                                        SSL_FILETYPE_PEM) <= 0)
87         return -1;
88       if (!SSL_CTX_check_private_key (tls_ctx))
89         return -1;
90     }
91
92   SSL_CTX_set_verify (tls_ctx, SSL_VERIFY_NONE, NULL);
93
94   return 0;
95 }
96
97 static int
98 tls_ssl_new(ctx, s)
99   SSL_CTX *ctx;
100   int s;
101 {
102   SSL_SESSION *session;
103   SSL_CIPHER *cipher;
104   X509   *peer;
105
106   tls_conn = (SSL *) SSL_new (ctx);
107   if (!tls_conn)
108     return -1;
109   SSL_clear(tls_conn);
110
111   if (!SSL_set_fd (tls_conn, s))
112     return -1;
113
114   SSL_set_connect_state (tls_conn);
115
116   if (SSL_connect (tls_conn) <= 0)
117     {
118       session = SSL_get_session (tls_conn);
119       if (session)
120         SSL_CTX_remove_session (ctx, session);
121       if (tls_conn!=NULL)
122         SSL_free (tls_conn);
123       return -1;
124     }
125
126   return 0;
127 }
128
129 static int
130 tls_connect (hostname, service)
131      const char *hostname, *service;
132 {
133   int server, false = 0;
134 #ifdef HAVE_ADDRINFO
135   struct addrinfo *in, *in0, hints;
136 #else
137   struct hostent *host;
138   struct servent *serv;
139   struct sockaddr_in sin;
140 #endif
141
142 #ifdef HAVE_ADDRINFO
143   memset (&hints, 0, sizeof (hints));
144   hints.ai_family = AF_UNSPEC;
145   hints.ai_socktype = SOCK_STREAM;
146   if (getaddrinfo (hostname, service, &hints, &in0))
147     return -1;
148
149   for (in = in0; in; in = in->ai_next)
150     {
151       server = socket (in->ai_family, in->ai_socktype, in->ai_protocol);
152       if (server < 0)
153         continue;
154       if (connect (server, in->ai_addr, in->ai_addrlen) < 0)
155         {
156           server = -1;
157           continue;
158         }
159       break;
160   }
161
162   if (server < 0)
163     return -1;
164 #else
165   memset (&sin, 0, sizeof (sin));
166   host = gethostbyname (hostname);
167   if (!host)
168     return -1;
169   memcpy (&sin.sin_addr, host->h_addr, host->h_length);
170   serv = getservbyname (service, "tcp");
171   if (serv)
172     sin.sin_port = serv->s_port;
173   else if (isdigit (service[0]))
174     sin.sin_port = htons (atoi (service));
175   sin.sin_family = AF_INET;
176   server = socket (sin.sin_family, SOCK_STREAM, 0);
177   if (server == -1)
178     return -1;
179
180   if (connect (server, (struct sockaddr *)&sin, sizeof (sin)) < 0)
181     {
182       close (server);
183       return -1;
184     }
185 #endif
186
187   setsockopt (server, SOL_SOCKET, SO_KEEPALIVE, (const char *) &false,
188               sizeof (false));
189
190   return server;
191 }
192
193 static void
194 tls_negotiate (sig)
195      int sig;
196 {
197   if (tls_ssl_ctx_new (opt_cert_file, opt_key_file) == -1)
198     return;
199
200   (void) tls_ssl_new (tls_ctx, tls_fd); /* Negotiation has done. */
201 }
202
203 static void
204 usage (progname)
205      const char *progname;
206 {
207   printf ("%s (%s) %s\n"
208           "Copyright (C) 1999 Free Software Foundation, Inc.\n"
209           "This program comes with ABSOLUTELY NO WARRANTY.\n"
210           "This is free software, and you are welcome to redistribute it\n"
211           "under certain conditions. See the file COPYING for details.\n\n"
212           "Usage: %s [options] host port\n\n"
213           "Options:\n\n"
214           " --cert-file [file]      specify certificate file\n"
215           " --key-file [file]       specify private key file\n"
216           " --verify [level]        set verification level\n",
217           progname, PACKAGE, VERSION, progname);
218 }
219      
220 int
221 main (argc, argv) 
222   int argc;
223   char **argv;
224 {
225   int in = fileno (stdin), out = fileno (stdout), 
226     nbuffer, wrote;
227 #ifdef HAVE_POLL
228   struct pollfd readfds[2], writefds[1];
229 #else
230   fd_set readfds, writefds;
231 #endif
232   char buffer[BUFSIZ], *retry;
233   struct sigaction act;
234
235   int this_option_optind = optind ? optind : 1;
236   int option_index = 0, c;
237   static struct option long_options[] =
238     {
239       {"cert-file", 1, 0, 'c'},
240       {"key-file", 1, 0, 'k'},
241       {"verify", 1, 0, 'v'},
242       {0, 0, 0, 0}
243     };
244
245   while (1)
246     {
247       c = getopt_long (argc, argv, "c:k:v:", long_options, &option_index);
248       if (c == -1)
249         break;
250     
251       switch (c)
252         {
253         case 'c':
254           opt_cert_file = optarg;
255           break;
256         case 'k':
257           opt_key_file = optarg;
258           break;
259         case 'v':
260           opt_verify = atoi (optarg);
261           break;
262         default:
263           usage (basename (argv[0]));
264           return 1;
265         }
266     }
267
268   if (optind+2 != argc)
269     {
270       usage (basename (argv[0]));
271       return 1;
272     }
273
274   tls_fd = tls_connect (argv[optind], argv[optind+1]);
275   if (tls_fd < 0)
276     {
277       perror ("tls_connect");
278       return 1;
279     }
280
281   memset (&act, 0, sizeof (act));
282   act.sa_handler = tls_negotiate;
283   sigemptyset (&act.sa_mask);
284   act.sa_flags = SA_RESTART|SA_RESETHAND;
285   sigaction (SIGALRM, &act, NULL);
286
287 #ifdef HAVE_POLL
288   readfds[0].fd = in;
289   readfds[1].fd = tls_fd;
290   readfds[0].events = POLLIN;
291   readfds[1].events = POLLIN;
292   writefds[0].events = POLLOUT;
293 #endif
294
295   while (1)
296     {
297 #ifdef HAVE_POLL
298       if (poll (readfds, 2, -1) == -1 && errno != EINTR)
299 #else
300       FD_ZERO (&readfds);
301       FD_SET (tls_fd, &readfds);
302       FD_SET (in, &readfds);
303       if (select (tls_fd+1, &readfds, NULL, NULL, NULL) == -1
304           && errno != EINTR )
305 #endif
306         {
307           perror ("poll");
308           return 1;
309         }
310 #ifdef HAVE_POLL
311       if (readfds[0].revents & POLLIN)
312 #else
313       if (FD_ISSET (in, &readfds))
314 #endif
315         {
316           nbuffer = read (in, buffer, sizeof buffer -1);
317
318           if (nbuffer == 0)
319             goto finish;
320           for (retry = buffer; nbuffer > 0; nbuffer -= wrote, retry += wrote)
321             {
322 #ifdef HAVE_POLL
323               writefds[0].fd = tls_fd;
324               if (poll (writefds, 1, -1) == -1)
325 #else
326               FD_ZERO (&writefds);
327               FD_SET (tls_fd, &writefds);
328               if (select (tls_fd+1, NULL, &writefds, NULL, NULL) == -1)
329 #endif
330                 {
331                   perror ("poll");
332                   return 1;
333                 }
334               if (tls_conn) 
335                 wrote = SSL_write (tls_conn, retry, nbuffer);
336               else
337                 wrote = write (tls_fd, retry, nbuffer);
338               if (wrote < 0) goto finish;
339             }
340         }
341 #ifdef HAVE_POLL
342       if (readfds[1].revents & POLLIN)
343 #else
344       if (FD_ISSET (tls_fd, &readfds))
345 #endif
346         {
347 readtop:
348           if (tls_conn)
349             nbuffer = SSL_read (tls_conn, buffer, sizeof buffer -1);
350           else
351             nbuffer = read (tls_fd, buffer, sizeof buffer -1);
352           if (nbuffer == 0)
353             goto finish;
354           for (retry = buffer; nbuffer > 0; nbuffer -= wrote, retry += wrote)
355             {
356 #ifdef HAVE_POLL
357               writefds[0].fd = out;
358               if (poll (writefds, 1, -1) == -1)
359 #else
360               FD_ZERO (&writefds);
361               FD_SET (out, &writefds);
362               if (select (out+1, NULL, &writefds, NULL, NULL) == -1)
363 #endif
364                 {
365                   perror ("poll");
366                   return 1;
367                 }
368               wrote = write (out, retry, nbuffer);
369               if (wrote < 0) goto finish;
370             }
371           if (tls_conn && SSL_pending(tls_conn))
372             goto readtop;
373         }
374     }
375
376  finish:
377   close (in);
378   close (out);
379   
380   return 0;
381 }