ntbtls-cli: Use union to access hostent addr.
[ntbtls.git] / src / ntbtls-cli.c
1 /* ntbtls-cli.h - NTBTLS client test program
2  * Copyright (C) 2014 g10 Code GmbH
3  *
4  * This file is part of NTBTLS
5  *
6  * NTBTLS is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * NTBTLS is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdarg.h>
25
26 #include <unistd.h>
27 #include <errno.h>
28 #include <sys/types.h>
29 #ifdef HAVE_W32_SYSTEM
30 # define WIN32_LEAN_AND_MEAN
31 # ifdef HAVE_WINSOCK2_H
32 #  include <winsock2.h>
33 # endif
34 # include <windows.h>
35 #else
36 # include <sys/socket.h>
37 # include <netinet/in.h>
38 # include <arpa/inet.h>
39 # include <netdb.h>
40 #endif
41
42 #include "ntbtls.h"
43
44 #define PGMNAME "ntbtls-cli"
45
46 static int verbose;
47 static int errorcount;
48 static char *opt_hostname;
49 static int opt_head;
50
51
52 \f
53 /*
54  * Reporting functions.
55  */
56 static void
57 die (const char *format, ...)
58 {
59   va_list arg_ptr ;
60
61   fflush (stdout);
62 #ifdef HAVE_FLOCKFILE
63   flockfile (stderr);
64 #endif
65   fprintf (stderr, "%s: ", PGMNAME);
66   va_start (arg_ptr, format) ;
67   vfprintf (stderr, format, arg_ptr);
68   va_end (arg_ptr);
69   if (*format && format[strlen(format)-1] != '\n')
70     putc ('\n', stderr);
71 #ifdef HAVE_FLOCKFILE
72   funlockfile (stderr);
73 #endif
74   exit (1);
75 }
76
77
78 static void
79 fail (const char *format, ...)
80 {
81   va_list arg_ptr;
82
83   fflush (stdout);
84 #ifdef HAVE_FLOCKFILE
85   flockfile (stderr);
86 #endif
87   fprintf (stderr, "%s: ", PGMNAME);
88   va_start (arg_ptr, format);
89   vfprintf (stderr, format, arg_ptr);
90   va_end (arg_ptr);
91   if (*format && format[strlen(format)-1] != '\n')
92     putc ('\n', stderr);
93 #ifdef HAVE_FLOCKFILE
94   funlockfile (stderr);
95 #endif
96   errorcount++;
97   if (errorcount >= 50)
98     die ("stopped after 50 errors.");
99 }
100
101
102 static void
103 info (const char *format, ...)
104 {
105   va_list arg_ptr;
106
107   if (!verbose)
108     return;
109 #ifdef HAVE_FLOCKFILE
110   flockfile (stderr);
111 #endif
112   fprintf (stderr, "%s: ", PGMNAME);
113   va_start (arg_ptr, format);
114   vfprintf (stderr, format, arg_ptr);
115   if (*format && format[strlen(format)-1] != '\n')
116     putc ('\n', stderr);
117   va_end (arg_ptr);
118 #ifdef HAVE_FLOCKFILE
119   funlockfile (stderr);
120 #endif
121 }
122
123
124 \f
125 /* Until we support send/recv in estream we need to use es_fopencookie
126  * under Windows.  */
127 #ifdef HAVE_W32_SYSTEM
128 static gpgrt_ssize_t
129 w32_cookie_read (void *cookie, void *buffer, size_t size)
130 {
131   int sock = (int)cookie;
132   int nread;
133
134   do
135     {
136       /* Under Windows we need to use recv for a socket.  */
137       nread = recv (sock, buffer, size, 0);
138     }
139   while (nread == -1 && errno == EINTR);
140
141   return (gpgrt_ssize_t)nread;
142 }
143
144 static gpg_error_t
145 w32_write_server (int sock, const char *data, size_t length)
146 {
147   int nleft;
148   int nwritten;
149
150   nleft = length;
151   while (nleft > 0)
152     {
153       nwritten = send (sock, data, nleft, 0);
154       if ( nwritten == SOCKET_ERROR )
155         {
156           info ("network write failed: ec=%d\n", (int)WSAGetLastError ());
157           return gpg_error (GPG_ERR_NETWORK);
158         }
159       nleft -= nwritten;
160       data += nwritten;
161     }
162
163   return 0;
164 }
165
166 /* Write handler for estream.  */
167 static gpgrt_ssize_t
168 w32_cookie_write (void *cookie, const void *buffer_arg, size_t size)
169 {
170   int sock = (int)cookie;
171   const char *buffer = buffer_arg;
172   int nwritten = 0;
173
174   if (w32_write_server (sock, buffer, size))
175     {
176       gpg_err_set_errno (EIO);
177       nwritten = -1;
178     }
179   else
180     nwritten = size;
181
182   return (gpgrt_ssize_t)nwritten;
183 }
184
185 static es_cookie_io_functions_t w32_cookie_functions =
186   {
187     w32_cookie_read,
188     w32_cookie_write,
189     NULL,
190     NULL
191   };
192 #endif /*HAVE_W32_SYSTEM*/
193
194
195 \f
196 static int
197 connect_server (const char *server, unsigned short port)
198 {
199   gpg_error_t err;
200   int sock = -1;
201   struct sockaddr_in addr;
202   struct hostent *host;
203   union {
204     char *addr;
205     struct in_addr *in_addr;
206   } addru;
207
208   addr.sin_family = AF_INET;
209   addr.sin_port = htons (port);
210   host = gethostbyname ((char*)server);
211   if (!host)
212     {
213       err = gpg_error_from_syserror ();
214       fail ("host '%s' not found: %s\n", server, gpg_strerror (err));
215       return -1;
216     }
217
218   addru.addr = host->h_addr;
219   addr.sin_addr = *addru.in_addr;
220
221   sock = socket (AF_INET, SOCK_STREAM, 0);
222   if (sock == -1)
223     {
224       err = gpg_error_from_syserror ();
225       die ("error creating socket: %s\n", gpg_strerror (err));
226       return -1;
227     }
228
229   if (connect (sock, (struct sockaddr *)&addr, sizeof addr) == -1)
230     {
231       err = gpg_error_from_syserror ();
232       fail ("error connecting '%s': %s\n", server, gpg_strerror (err));
233       close (sock);
234       return -1;
235     }
236
237   info ("connected to '%s' port %hu\n", server, port);
238
239   return sock;
240 }
241
242
243 static int
244 connect_estreams (const char *server, int port,
245                   estream_t *r_in, estream_t *r_out)
246 {
247   gpg_error_t err;
248   int sock;
249
250   *r_in = *r_out = NULL;
251
252   sock = connect_server (server, port);
253   if (sock == -1)
254     return gpg_error (GPG_ERR_GENERAL);
255
256 #ifdef HAVE_W32_SYSTEM
257   *r_in = es_fopencookie ((void*)(unsigned int)sock, "rb",
258                           w32_cookie_functions);
259 #else
260   *r_in = es_fdopen (sock, "rb");
261 #endif
262   if (!*r_in)
263     {
264       err = gpg_error_from_syserror ();
265       close (sock);
266       return err;
267     }
268 #ifdef HAVE_W32_SYSTEM
269   *r_out = es_fopencookie ((void*)(unsigned int)sock, "wb",
270                            w32_cookie_functions);
271 #else
272   *r_out = es_fdopen (sock, "wb");
273 #endif
274   if (!*r_out)
275     {
276       err = gpg_error_from_syserror ();
277       es_fclose (*r_in);
278       *r_in = NULL;
279       close (sock);
280       return err;
281     }
282
283   return 0;
284 }
285
286
287 \f
288 static void
289 simple_client (const char *server, int port)
290 {
291   gpg_error_t err;
292   ntbtls_t tls;
293   estream_t inbound, outbound;
294   estream_t readfp, writefp;
295   int c;
296
297   err = ntbtls_new (&tls, NTBTLS_CLIENT);
298   if (err)
299     die ("ntbtls_init failed: %s <%s>\n",
300          gpg_strerror (err), gpg_strsource (err));
301
302   err = connect_estreams (server, port, &inbound, &outbound);
303   if (err)
304     die ("error connecting server: %s <%s>\n",
305          gpg_strerror (err), gpg_strsource (err));
306
307   err = ntbtls_set_transport (tls, inbound, outbound);
308   if (err)
309     die ("ntbtls_set_transport failed: %s <%s>\n",
310          gpg_strerror (err), gpg_strsource (err));
311
312   err = ntbtls_get_stream (tls, &readfp, &writefp);
313   if (err)
314     die ("ntbtls_get_stream failed: %s <%s>\n",
315          gpg_strerror (err), gpg_strsource (err));
316
317   if (opt_hostname)
318     {
319       err = ntbtls_set_hostname (tls, opt_hostname);
320       if (err)
321         die ("ntbtls_set_hostname failed: %s <%s>\n",
322              gpg_strerror (err), gpg_strsource (err));
323     }
324
325   info ("starting handshake");
326   while ((err = ntbtls_handshake (tls)))
327     {
328       info ("handshake error: %s <%s>", gpg_strerror (err),gpg_strsource (err));
329       switch (gpg_err_code (err))
330         {
331         default:
332           break;
333         }
334       die ("handshake failed");
335     }
336   info ("handshake done");
337
338   do
339     {
340       es_fprintf (writefp, "%s / HTTP/1.0\r\n", opt_head? "HEAD":"GET");
341       if (opt_hostname)
342         es_fprintf (writefp, "Host: %s\r\n", opt_hostname);
343       es_fprintf (writefp, "X-ntbtls: %s\r\n",
344                   ntbtls_check_version (PACKAGE_VERSION));
345       es_fputs ("\r\n", writefp);
346       es_fflush (writefp);
347       while (/*es_pending (readfp) &&*/ (c = es_fgetc (readfp)) != EOF)
348         putchar (c);
349     }
350   while (c != EOF);
351
352   ntbtls_release (tls);
353   es_fclose (inbound);
354   es_fclose (outbound);
355 }
356
357
358
359 int
360 main (int argc, char **argv)
361 {
362   int last_argc = -1;
363   int debug_level = 0;
364   int port = 443;
365   char *host;
366
367   if (argc)
368     { argc--; argv++; }
369   while (argc && last_argc != argc )
370     {
371       last_argc = argc;
372       if (!strcmp (*argv, "--"))
373         {
374           argc--; argv++;
375           break;
376         }
377       else if (!strcmp (*argv, "--help"))
378         {
379           fputs ("Usage: " PGMNAME " [OPTIONS] HOST\n"
380                  "Connect via TLS to HOST\n"
381                  "Options:\n"
382                  "  --version       print the library version\n"
383                  "  --verbose       show more diagnostics\n"
384                  "  --debug LEVEL   enable debugging at LEVEL\n"
385                  "  --port N        connect to port N (default is 443)\n"
386                  "  --hostname NAME use NAME instead of HOST for SNI\n"
387                  "  --head          send a HEAD and not a GET request\n"
388                  "\n", stdout);
389           return 0;
390         }
391       else if (!strcmp (*argv, "--version"))
392         {
393           printf ("%s\n", ntbtls_check_version (NULL));
394           if (verbose)
395             printf ("%s", ntbtls_check_version ("\001\001"));
396           return 0;
397         }
398       else if (!strcmp (*argv, "--verbose"))
399         {
400           verbose = 1;
401           argc--; argv++;
402         }
403       else if (!strcmp (*argv, "--debug"))
404         {
405           verbose = 1;
406           argc--; argv++;
407           if (argc)
408             {
409               debug_level = atoi (*argv);
410               argc--; argv++;
411             }
412           else
413             debug_level = 1;
414         }
415       else if (!strcmp (*argv, "--port"))
416         {
417           argc--; argv++;
418           if (argc)
419             {
420               port = atoi (*argv);
421               argc--; argv++;
422             }
423           else
424             port = 8443;
425         }
426       else if (!strcmp (*argv, "--hostname"))
427         {
428           if (argc < 2)
429             die ("argument missing for option '%s'\n", *argv);
430           argc--; argv++;
431           opt_hostname = *argv;
432           argc--; argv++;
433         }
434       else if (!strcmp (*argv, "--head"))
435         {
436           opt_head = 1;
437           argc--; argv++;
438         }
439       else if (!strncmp (*argv, "--", 2) && (*argv)[2])
440         die ("Invalid option '%s'\n", *argv);
441     }
442
443   host = argc? *argv : "localhost";
444   if (!opt_hostname)
445     opt_hostname = host;
446   if (!*opt_hostname)
447     opt_hostname = NULL;
448
449 #ifdef HAVE_W32_SYSTEM
450   {
451     WSADATA wsadat;
452     WSAStartup (0x202, &wsadat);
453   }
454 #endif
455
456   if (!ntbtls_check_version (PACKAGE_VERSION))
457     die ("NTBTLS library too old (need %s, have %s)\n",
458          PACKAGE_VERSION, ntbtls_check_version (NULL));
459
460   if (debug_level)
461     ntbtls_set_debug (debug_level, NULL, NULL);
462
463   simple_client (host, port);
464   return 0;
465 }