release.
[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   struct protoent *proto;
134   int server, false = 0, family, socktype;
135   struct sockaddr *addr;
136   struct sockaddr_in sin;
137   socklen_t addrlen;
138 #ifdef HAVE_ADDRINFO
139   struct addrinfo *in, hints;
140 #else
141   struct hostent *host;
142   struct servent serv;
143 #endif
144
145   proto = getprotobyname ("tcp");
146   if (!proto)
147     return -1;
148
149 #ifdef HAVE_ADDRINFO
150   memset (&hints, 0, sizeof (hints));
151   hints.ai_family = AF_UNSPEC;
152   hints.ai_socktype = SOCK_STREAM;
153   hints.ai_protocol = proto->p_proto;
154
155   if (getaddrinfo (hostname, service, &hints, &in) < 0) 
156     return -1;
157
158   family = in->ai_family;
159   socktype = in->ai_socktype;
160   addr = in->ai_addr;
161   addrlen = in->ai_addrlen;
162   freeaddrinfo (in);
163 #else
164   memset (&sin, 0, sizeof (sin));
165   host = gethostbyname (hostname);
166   if (!host)
167     return -1;
168   memcpy (&sin.sin_addr, host->h_addr, host->h_length);
169   serv = getservbyname (service, proto->p_name);
170   if (serv)
171     sin.sin_port = htons (serv->s_port);
172   else if (isdigit (service[0]))
173     sin.sin_port = htons (atoi (service));
174   family = sin.sin_family = AF_INET;
175   socktype = SOCK_STREAM;
176   addr = (struct sockaddr *)&sin;
177   addrlen = sizeof (sin);
178 #endif
179
180   server = socket (family, socktype, 0);
181   if (server == -1)
182     return -1;
183
184   setsockopt (server, SOL_SOCKET, SO_KEEPALIVE, (const char *) &false,
185               sizeof (false));
186
187   if (connect (server, addr, addrlen) < 0)
188     {
189       close (server);
190       return -1;
191     }
192
193   return server;
194 }
195
196 static void
197 tls_negotiate (sig)
198      int sig;
199 {
200   if (tls_ssl_ctx_new (opt_cert_file, opt_key_file) == -1)
201     return;
202
203   (void) tls_ssl_new (tls_ctx, tls_fd); /* Negotiation has done. */
204 }
205
206 static void
207 usage (progname)
208      const char *progname;
209 {
210   printf ("%s (%s) %s\n"
211           "Copyright (C) 1999 Free Software Foundation, Inc.\n"
212           "This program comes with ABSOLUTELY NO WARRANTY.\n"
213           "This is free software, and you are welcome to redistribute it\n"
214           "under certain conditions. See the file COPYING for details.\n\n"
215           "Usage: %s [options] host port\n\n"
216           "Options:\n\n"
217           " --cert-file [file]      specify certificate file\n"
218           " --key-file [file]       specify private key file\n"
219           " --verify [level]        set verification level\n",
220           progname, PACKAGE, VERSION, progname);
221 }
222      
223 int
224 main (argc, argv) 
225   int argc;
226   char **argv;
227 {
228   int in = fileno (stdin), out = fileno (stdout), 
229     nbuffer, wrote;
230 #ifdef HAVE_POLL
231   struct pollfd readfds[2], writefds[1];
232 #else
233   fd_set readfds, writefds;
234 #endif
235   char buffer[BUFSIZ], *retry;
236   struct sigaction act;
237
238   int this_option_optind = optind ? optind : 1;
239   int option_index = 0, c;
240   static struct option long_options[] =
241     {
242       {"cert-file", 1, 0, 'c'},
243       {"key-file", 1, 0, 'k'},
244       {"verify", 1, 0, 'v'},
245       {0, 0, 0, 0}
246     };
247
248   while (1)
249     {
250       c = getopt_long (argc, argv, "c:k:v:", long_options, &option_index);
251       if (c == -1)
252         break;
253     
254       switch (c)
255         {
256         case 'c':
257           opt_cert_file = optarg;
258           break;
259         case 'k':
260           opt_key_file = optarg;
261           break;
262         case 'v':
263           opt_verify = atoi (optarg);
264           break;
265         default:
266           usage (basename (argv[0]));
267           return 1;
268         }
269     }
270
271   if (optind+2 != argc)
272     {
273       usage (basename (argv[0]));
274       return 1;
275     }
276
277   tls_fd = tls_connect (argv[optind], argv[optind+1]);
278   if (tls_fd < 0)
279     {
280       perror ("tls_connect");
281       return 1;
282     }
283
284   memset (&act, 0, sizeof (act));
285   act.sa_handler = tls_negotiate;
286   sigemptyset (&act.sa_mask);
287   act.sa_flags = SA_RESTART|SA_RESETHAND;
288   sigaction (SIGALRM, &act, NULL);
289
290 #ifdef HAVE_POLL
291   readfds[0].fd = in;
292   readfds[1].fd = tls_fd;
293   readfds[0].events = POLLIN;
294   readfds[1].events = POLLIN;
295   writefds[0].events = POLLOUT;
296 #endif
297
298   while (1)
299     {
300 #ifdef HAVE_POLL
301       if (poll (readfds, 2, -1) == -1 && errno != EINTR)
302 #else
303       FD_ZERO (&readfds);
304       FD_SET (tls_fd, &readfds);
305       FD_SET (in, &readfds);
306       if (select (tls_fd+1, &readfds, NULL, NULL, NULL) == -1
307           && errno != EINTR )
308 #endif
309         {
310           perror ("poll");
311           return 1;
312         }
313 #ifdef HAVE_POLL
314       if (readfds[0].revents & POLLIN)
315 #else
316       if (FD_ISSET (in, &readfds))
317 #endif
318         {
319           nbuffer = read (in, buffer, sizeof buffer -1);
320
321           if (nbuffer == 0)
322             goto finish;
323           for (retry = buffer; nbuffer > 0; nbuffer -= wrote, retry += wrote)
324             {
325 #ifdef HAVE_POLL
326               writefds[0].fd = tls_fd;
327               if (poll (writefds, 1, -1) == -1)
328 #else
329               FD_ZERO (&writefds);
330               FD_SET (tls_fd, &writefds);
331               if (select (tls_fd+1, NULL, &writefds, NULL, NULL) == -1)
332 #endif
333                 {
334                   perror ("poll");
335                   return 1;
336                 }
337               if (tls_conn) 
338                 wrote = SSL_write (tls_conn, retry, nbuffer);
339               else
340                 wrote = write (tls_fd, retry, nbuffer);
341               if (wrote < 0) goto finish;
342             }
343         }
344 #ifdef HAVE_POLL
345       if (readfds[1].revents & POLLIN)
346 #else
347       if (FD_ISSET (tls_fd, &readfds))
348 #endif
349         {
350           if (tls_conn)
351             nbuffer = SSL_read (tls_conn, buffer, sizeof buffer -1);
352           else
353             nbuffer = read (tls_fd, buffer, sizeof buffer -1);
354           if (nbuffer == 0)
355             goto finish;
356           for (retry = buffer; nbuffer > 0; nbuffer -= wrote, retry += wrote)
357             {
358 #ifdef HAVE_POLL
359               writefds[0].fd = out;
360               if (poll (writefds, 1, -1) == -1)
361 #else
362               FD_ZERO (&writefds);
363               FD_SET (out, &writefds);
364               if (select (out+1, NULL, &writefds, NULL, NULL) == -1)
365 #endif
366                 {
367                   perror ("poll");
368                   return 1;
369                 }
370               wrote = write (out, retry, nbuffer);
371               if (wrote < 0) goto finish;
372             }
373         }
374     }
375
376  finish:
377   close (in);
378   close (out);
379   
380   return 0;
381 }