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