46264cf00b4d1ceb14d581f22c1d8dc1e2a534b2
[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 <sys/types.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 #include <netdb.h>
32
33 #include "ntbtls.h"
34
35 #define PGMNAME "ntbtls-cli"
36
37 static int verbose;
38 static int errorcount;
39 static char *opt_hostname;
40
41 \f
42 /*
43  * Reporting functions.
44  */
45 static void
46 die (const char *format, ...)
47 {
48   va_list arg_ptr ;
49
50   fflush (stdout);
51 #ifdef HAVE_FLOCKFILE
52   flockfile (stderr);
53 #endif
54   fprintf (stderr, "%s: ", PGMNAME);
55   va_start (arg_ptr, format) ;
56   vfprintf (stderr, format, arg_ptr);
57   va_end (arg_ptr);
58   if (*format && format[strlen(format)-1] != '\n')
59     putc ('\n', stderr);
60 #ifdef HAVE_FLOCKFILE
61   funlockfile (stderr);
62 #endif
63   exit (1);
64 }
65
66
67 static void
68 fail (const char *format, ...)
69 {
70   va_list arg_ptr;
71
72   fflush (stdout);
73 #ifdef HAVE_FLOCKFILE
74   flockfile (stderr);
75 #endif
76   fprintf (stderr, "%s: ", PGMNAME);
77   va_start (arg_ptr, format);
78   vfprintf (stderr, format, arg_ptr);
79   va_end (arg_ptr);
80   if (*format && format[strlen(format)-1] != '\n')
81     putc ('\n', stderr);
82 #ifdef HAVE_FLOCKFILE
83   funlockfile (stderr);
84 #endif
85   errorcount++;
86   if (errorcount >= 50)
87     die ("stopped after 50 errors.");
88 }
89
90
91 static void
92 info (const char *format, ...)
93 {
94   va_list arg_ptr;
95
96   if (!verbose)
97     return;
98 #ifdef HAVE_FLOCKFILE
99   flockfile (stderr);
100 #endif
101   fprintf (stderr, "%s: ", PGMNAME);
102   va_start (arg_ptr, format);
103   vfprintf (stderr, format, arg_ptr);
104   if (*format && format[strlen(format)-1] != '\n')
105     putc ('\n', stderr);
106   va_end (arg_ptr);
107 #ifdef HAVE_FLOCKFILE
108   funlockfile (stderr);
109 #endif
110 }
111
112
113 \f
114 static int
115 connect_server (const char *server, unsigned short port)
116 {
117   gpg_error_t err;
118   int sock = -1;
119   struct sockaddr_in addr;
120   struct hostent *host;
121
122   addr.sin_family = AF_INET;
123   addr.sin_port = htons (port);
124   host = gethostbyname ((char*)server);
125   if (!host)
126     {
127       err = gpg_error_from_syserror ();
128       fail ("host '%s' not found: %s\n", server, gpg_strerror (err));
129       return -1;
130     }
131
132   addr.sin_addr = *(struct in_addr*)host->h_addr;
133
134   sock = socket (AF_INET, SOCK_STREAM, 0);
135   if (sock == -1)
136     {
137       err = gpg_error_from_syserror ();
138       die ("error creating socket: %s\n", gpg_strerror (err));
139       return -1;
140     }
141
142   if (connect (sock, (struct sockaddr *)&addr, sizeof addr) == -1)
143     {
144       err = gpg_error_from_syserror ();
145       fail ("error connecting '%s': %s\n", server, gpg_strerror (err));
146       close (sock);
147       return -1;
148     }
149
150   info ("connected to '%s' port %hu\n", server, port);
151
152   return sock;
153 }
154
155
156 static int
157 connect_estreams (const char *server, int port,
158                   estream_t *r_in, estream_t *r_out)
159 {
160   gpg_error_t err;
161   int sock;
162
163   *r_in = *r_out = NULL;
164
165   sock = connect_server (server, port);
166   if (sock == -1)
167     return gpg_error (GPG_ERR_GENERAL);
168   *r_in = es_fdopen_nc (sock, "rb");
169   if (!*r_in)
170     {
171       err = gpg_error_from_syserror ();
172       close (sock);
173       return err;
174     }
175   *r_out = es_fdopen (sock, "wb");
176   if (!*r_out)
177     {
178       err = gpg_error_from_syserror ();
179       es_fclose (*r_in);
180       *r_in = NULL;
181       close (sock);
182       return err;
183     }
184
185   return 0;
186 }
187
188
189 \f
190 static void
191 simple_client (const char *server, int port)
192 {
193   gpg_error_t err;
194   ntbtls_t tls;
195   estream_t inbound, outbound;
196   estream_t readfp, writefp;
197   int c;
198
199   err = ntbtls_new (&tls, NTBTLS_CLIENT);
200   if (err)
201     die ("ntbtls_init failed: %s <%s>\n",
202          gpg_strerror (err), gpg_strsource (err));
203
204   err = connect_estreams (server, port, &inbound, &outbound);
205   if (err)
206     die ("error connecting server: %s <%s>\n",
207          gpg_strerror (err), gpg_strsource (err));
208
209   err = ntbtls_set_transport (tls, inbound, outbound);
210   if (err)
211     die ("ntbtls_set_transport failed: %s <%s>\n",
212          gpg_strerror (err), gpg_strsource (err));
213
214   err = ntbtls_get_stream (tls, &readfp, &writefp);
215   if (err)
216     die ("ntbtls_get_stream failed: %s <%s>\n",
217          gpg_strerror (err), gpg_strsource (err));
218
219   if (opt_hostname)
220     {
221       err = ntbtls_set_hostname (tls, opt_hostname);
222       if (err)
223         die ("ntbtls_set_hostname failed: %s <%s>\n",
224              gpg_strerror (err), gpg_strsource (err));
225     }
226
227   info ("starting handshake");
228   while ((err = ntbtls_handshake (tls)))
229     {
230       info ("handshake error: %s <%s>", gpg_strerror (err),gpg_strsource (err));
231       switch (gpg_err_code (err))
232         {
233         default:
234           break;
235         }
236       die ("handshake failed");
237     }
238   info ("handshake done");
239
240   do
241     {
242       es_fputs ("GET / HTTP/1.0\r\n", writefp);
243       if (opt_hostname)
244         es_fprintf (writefp, "Host: %s\r\n", opt_hostname);
245       es_fprintf (writefp, "X-ntbtls: %s\r\n",
246                   ntbtls_check_version (PACKAGE_VERSION));
247       es_fputs ("\r\n", writefp);
248       es_fflush (writefp);
249       while (/*es_pending (readfp) &&*/ (c = es_fgetc (readfp)) != EOF)
250         putchar (c);
251     }
252   while (c != EOF);
253
254   ntbtls_release (tls);
255   es_fclose (inbound);
256   es_fclose (outbound);
257 }
258
259
260
261 int
262 main (int argc, char **argv)
263 {
264   int last_argc = -1;
265   int debug_level = 0;
266   int port = 443;
267
268   if (argc)
269     { argc--; argv++; }
270   while (argc && last_argc != argc )
271     {
272       last_argc = argc;
273       if (!strcmp (*argv, "--"))
274         {
275           argc--; argv++;
276           break;
277         }
278       else if (!strcmp (*argv, "--help"))
279         {
280           fputs ("Usage: " PGMNAME " [OPTIONS] HOST\n"
281                  "Connect via TLS to HOST\n"
282                  "Options:\n"
283                  "  --version       print the library version\n"
284                  "  --verbose       show more diagnostics\n"
285                  "  --debug LEVEL   enable debugging at LEVEL\n"
286                  "  --port N        connect to port N (default is 443)\n"
287                  "  --hostname NAME use NAME for SNI\n"
288                  "\n", stdout);
289           return 0;
290         }
291       else if (!strcmp (*argv, "--version"))
292         {
293           printf ("%s\n", ntbtls_check_version (NULL));
294           if (verbose)
295             printf ("%s", ntbtls_check_version ("\001\001"));
296           return 0;
297         }
298       else if (!strcmp (*argv, "--verbose"))
299         {
300           verbose = 1;
301           argc--; argv++;
302         }
303       else if (!strcmp (*argv, "--debug"))
304         {
305           verbose = 1;
306           argc--; argv++;
307           if (argc)
308             {
309               debug_level = atoi (*argv);
310               argc--; argv++;
311             }
312           else
313             debug_level = 1;
314         }
315       else if (!strcmp (*argv, "--port"))
316         {
317           argc--; argv++;
318           if (argc)
319             {
320               port = atoi (*argv);
321               argc--; argv++;
322             }
323           else
324             port = 8443;
325         }
326       else if (!strcmp (*argv, "--hostname"))
327         {
328           if (argc < 2)
329             die ("argument missing for option '%s'\n", *argv);
330           argc--; argv++;
331           opt_hostname = *argv;
332           argc--; argv++;
333         }
334       else if (!strncmp (*argv, "--", 2) && (*argv)[2])
335         die ("Invalid option '%s'\n", *argv);
336     }
337
338   if (!ntbtls_check_version (PACKAGE_VERSION))
339     die ("NTBTLS library too old (need %s, have %s)\n",
340          PACKAGE_VERSION, ntbtls_check_version (NULL));
341
342   if (debug_level)
343     ntbtls_set_debug (debug_level, NULL, NULL);
344
345   simple_client (argc? *argv : "localhost", port);
346   return 0;
347 }