]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/ntp/sntp/libevent/sample/https-client.c
Fix a regression with SA-15:24 patch that prevented NIS from
[FreeBSD/releng/10.2.git] / contrib / ntp / sntp / libevent / sample / https-client.c
1 /*
2   This is an example of how to hook up evhttp with bufferevent_ssl
3
4   It just GETs an https URL given on the command-line and prints the response
5   body to stdout.
6
7   Actually, it also accepts plain http URLs to make it easy to compare http vs
8   https code paths.
9
10   Loosely based on le-proxy.c.
11  */
12
13 // Get rid of OSX 10.7 and greater deprecation warnings.
14 #if defined(__APPLE__) && defined(__clang__)
15 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
16 #endif
17
18 #include <stdio.h>
19 #include <assert.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <errno.h>
23
24 #ifdef _WIN32
25 #include <winsock2.h>
26 #include <ws2tcpip.h>
27
28 #define snprintf _snprintf
29 #define strcasecmp _stricmp 
30 #else
31 #include <sys/socket.h>
32 #include <netinet/in.h>
33 #endif
34
35 #include <event2/bufferevent_ssl.h>
36 #include <event2/bufferevent.h>
37 #include <event2/buffer.h>
38 #include <event2/listener.h>
39 #include <event2/util.h>
40 #include <event2/http.h>
41
42 #include <openssl/ssl.h>
43 #include <openssl/err.h>
44 #include <openssl/rand.h>
45
46 #include "openssl_hostname_validation.h"
47
48 static struct event_base *base;
49 static int ignore_cert = 0;
50
51 static void
52 http_request_done(struct evhttp_request *req, void *ctx)
53 {
54         char buffer[256];
55         int nread;
56
57         if (req == NULL) {
58                 /* If req is NULL, it means an error occurred, but
59                  * sadly we are mostly left guessing what the error
60                  * might have been.  We'll do our best... */
61                 struct bufferevent *bev = (struct bufferevent *) ctx;
62                 unsigned long oslerr;
63                 int printed_err = 0;
64                 int errcode = EVUTIL_SOCKET_ERROR();
65                 fprintf(stderr, "some request failed - no idea which one though!\n");
66                 /* Print out the OpenSSL error queue that libevent
67                  * squirreled away for us, if any. */
68                 while ((oslerr = bufferevent_get_openssl_error(bev))) {
69                         ERR_error_string_n(oslerr, buffer, sizeof(buffer));
70                         fprintf(stderr, "%s\n", buffer);
71                         printed_err = 1;
72                 }
73                 /* If the OpenSSL error queue was empty, maybe it was a
74                  * socket error; let's try printing that. */
75                 if (! printed_err)
76                         fprintf(stderr, "socket error = %s (%d)\n",
77                                 evutil_socket_error_to_string(errcode),
78                                 errcode);
79                 return;
80         }
81
82         fprintf(stderr, "Response line: %d %s\n",
83             evhttp_request_get_response_code(req),
84             evhttp_request_get_response_code_line(req));
85
86         while ((nread = evbuffer_remove(evhttp_request_get_input_buffer(req),
87                     buffer, sizeof(buffer)))
88                > 0) {
89                 /* These are just arbitrary chunks of 256 bytes.
90                  * They are not lines, so we can't treat them as such. */
91                 fwrite(buffer, nread, 1, stdout);
92         }
93 }
94
95 static void
96 syntax(void)
97 {
98         fputs("Syntax:\n", stderr);
99         fputs("   https-client -url <https-url> [-data data-file.bin] [-ignore-cert] [-retries num]\n", stderr);
100         fputs("Example:\n", stderr);
101         fputs("   https-client -url https://ip.appspot.com/\n", stderr);
102
103         exit(1);
104 }
105
106 static void
107 die(const char *msg)
108 {
109         fputs(msg, stderr);
110         exit(1);
111 }
112
113 static void
114 die_openssl(const char *func)
115 {
116         fprintf (stderr, "%s failed:\n", func);
117
118         /* This is the OpenSSL function that prints the contents of the
119          * error stack to the specified file handle. */
120         ERR_print_errors_fp (stderr);
121
122         exit(1);
123 }
124
125 /* See http://archives.seul.org/libevent/users/Jan-2013/msg00039.html */
126 static int cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg)
127 {
128         char cert_str[256];
129         const char *host = (const char *) arg;
130         const char *res_str = "X509_verify_cert failed";
131         HostnameValidationResult res = Error;
132
133         /* This is the function that OpenSSL would call if we hadn't called
134          * SSL_CTX_set_cert_verify_callback().  Therefore, we are "wrapping"
135          * the default functionality, rather than replacing it. */
136         int ok_so_far = 0;
137
138         X509 *server_cert = NULL;
139
140         if (ignore_cert) {
141                 return 1;
142         }
143
144         ok_so_far = X509_verify_cert(x509_ctx);
145
146         server_cert = X509_STORE_CTX_get_current_cert(x509_ctx);
147
148         if (ok_so_far) {
149                 res = validate_hostname(host, server_cert);
150
151                 switch (res) {
152                 case MatchFound:
153                         res_str = "MatchFound";
154                         break;
155                 case MatchNotFound:
156                         res_str = "MatchNotFound";
157                         break;
158                 case NoSANPresent:
159                         res_str = "NoSANPresent";
160                         break;
161                 case MalformedCertificate:
162                         res_str = "MalformedCertificate";
163                         break;
164                 case Error:
165                         res_str = "Error";
166                         break;
167                 default:
168                         res_str = "WTF!";
169                         break;
170                 }
171         }
172
173         X509_NAME_oneline(X509_get_subject_name (server_cert),
174                           cert_str, sizeof (cert_str));
175
176         if (res == MatchFound) {
177                 printf("https server '%s' has this certificate, "
178                        "which looks good to me:\n%s\n",
179                        host, cert_str);
180                 return 1;
181         } else {
182                 printf("Got '%s' for hostname '%s' and certificate:\n%s\n",
183                        res_str, host, cert_str);
184                 return 0;
185         }
186 }
187
188 int
189 main(int argc, char **argv)
190 {
191         int r;
192
193         struct evhttp_uri *http_uri;
194         const char *url = NULL, *data_file = NULL;
195         const char *scheme, *host, *path, *query;
196         char uri[256];
197         int port;
198         int retries = 0;
199
200         SSL_CTX *ssl_ctx;
201         SSL *ssl;
202         struct bufferevent *bev;
203         struct evhttp_connection *evcon;
204         struct evhttp_request *req;
205         struct evkeyvalq *output_headers;
206         struct evbuffer * output_buffer;
207
208         int i;
209
210         for (i = 1; i < argc; i++) {
211                 if (!strcmp("-url", argv[i])) {
212                         if (i < argc - 1) {
213                                 url = argv[i + 1];
214                         } else {
215                                 syntax();
216                         }
217                 } else if (!strcmp("-ignore-cert", argv[i])) {
218                         ignore_cert = 1;
219                 } else if (!strcmp("-data", argv[i])) {
220                         if (i < argc - 1) {
221                                 data_file = argv[i + 1];
222                         } else {
223                                 syntax();
224                         }
225                 } else if (!strcmp("-retries", argv[i])) {
226                         if (i < argc - 1) {
227                                 retries = atoi(argv[i + 1]);
228                         } else {
229                                 syntax();
230                         }
231                 } else if (!strcmp("-help", argv[i])) {
232                         syntax();
233                 }
234         }
235
236         if (!url) {
237                 syntax();
238         }
239
240 #ifdef _WIN32
241         {
242                 WORD wVersionRequested;
243                 WSADATA wsaData;
244                 int err;
245
246                 wVersionRequested = MAKEWORD(2, 2);
247
248                 err = WSAStartup(wVersionRequested, &wsaData);
249                 if (err != 0) {
250                         printf("WSAStartup failed with error: %d\n", err);
251                         return 1;
252                 }
253         }
254 #endif // _WIN32
255
256         http_uri = evhttp_uri_parse(url);
257         if (http_uri == NULL) {
258                 die("malformed url");
259         }
260
261         scheme = evhttp_uri_get_scheme(http_uri);
262         if (scheme == NULL || (strcasecmp(scheme, "https") != 0 &&
263                                strcasecmp(scheme, "http") != 0)) {
264                 die("url must be http or https");
265         }
266
267         host = evhttp_uri_get_host(http_uri);
268         if (host == NULL) {
269                 die("url must have a host");
270         }
271
272         port = evhttp_uri_get_port(http_uri);
273         if (port == -1) {
274                 port = (strcasecmp(scheme, "http") == 0) ? 80 : 443;
275         }
276
277         path = evhttp_uri_get_path(http_uri);
278         if (path == NULL) {
279                 path = "/";
280         }
281
282         query = evhttp_uri_get_query(http_uri);
283         if (query == NULL) {
284                 snprintf(uri, sizeof(uri) - 1, "%s", path);
285         } else {
286                 snprintf(uri, sizeof(uri) - 1, "%s?%s", path, query);
287         }
288         uri[sizeof(uri) - 1] = '\0';
289
290         // Initialize OpenSSL
291         SSL_library_init();
292         ERR_load_crypto_strings();
293         SSL_load_error_strings();
294         OpenSSL_add_all_algorithms();
295
296         /* This isn't strictly necessary... OpenSSL performs RAND_poll
297          * automatically on first use of random number generator. */
298         r = RAND_poll();
299         if (r == 0) {
300                 die_openssl("RAND_poll");
301         }
302
303         /* Create a new OpenSSL context */
304         ssl_ctx = SSL_CTX_new(SSLv23_method());
305         if (!ssl_ctx)
306                 die_openssl("SSL_CTX_new");
307
308         #ifndef _WIN32
309         /* TODO: Add certificate loading on Windows as well */
310
311         /* Attempt to use the system's trusted root certificates.
312          * (This path is only valid for Debian-based systems.) */
313         if (1 != SSL_CTX_load_verify_locations(ssl_ctx,
314                                                "/etc/ssl/certs/ca-certificates.crt",
315                                                NULL))
316                 die_openssl("SSL_CTX_load_verify_locations");
317         /* Ask OpenSSL to verify the server certificate.  Note that this
318          * does NOT include verifying that the hostname is correct.
319          * So, by itself, this means anyone with any legitimate
320          * CA-issued certificate for any website, can impersonate any
321          * other website in the world.  This is not good.  See "The
322          * Most Dangerous Code in the World" article at
323          * https://crypto.stanford.edu/~dabo/pubs/abstracts/ssl-client-bugs.html
324          */
325         SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
326         /* This is how we solve the problem mentioned in the previous
327          * comment.  We "wrap" OpenSSL's validation routine in our
328          * own routine, which also validates the hostname by calling
329          * the code provided by iSECPartners.  Note that even though
330          * the "Everything You've Always Wanted to Know About
331          * Certificate Validation With OpenSSL (But Were Afraid to
332          * Ask)" paper from iSECPartners says very explicitly not to
333          * call SSL_CTX_set_cert_verify_callback (at the bottom of
334          * page 2), what we're doing here is safe because our
335          * cert_verify_callback() calls X509_verify_cert(), which is
336          * OpenSSL's built-in routine which would have been called if
337          * we hadn't set the callback.  Therefore, we're just
338          * "wrapping" OpenSSL's routine, not replacing it. */
339         SSL_CTX_set_cert_verify_callback (ssl_ctx, cert_verify_callback,
340                                           (void *) host);
341         #endif // not _WIN32
342
343         // Create event base
344         base = event_base_new();
345         if (!base) {
346                 perror("event_base_new()");
347                 return 1;
348         }
349
350         // Create OpenSSL bufferevent and stack evhttp on top of it
351         ssl = SSL_new(ssl_ctx);
352         if (ssl == NULL) {
353                 die_openssl("SSL_new()");
354         }
355
356         #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
357         // Set hostname for SNI extension
358         SSL_set_tlsext_host_name(ssl, host);
359         #endif
360
361         if (strcasecmp(scheme, "http") == 0) {
362                 bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
363         } else {
364                 bev = bufferevent_openssl_socket_new(base, -1, ssl,
365                         BUFFEREVENT_SSL_CONNECTING,
366                         BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
367         }
368
369         if (bev == NULL) {
370                 fprintf(stderr, "bufferevent_openssl_socket_new() failed\n");
371                 return 1;
372         }
373
374         bufferevent_openssl_set_allow_dirty_shutdown(bev, 1);
375
376         // For simplicity, we let DNS resolution block. Everything else should be
377         // asynchronous though.
378         evcon = evhttp_connection_base_bufferevent_new(base, NULL, bev,
379                 host, port);
380         if (evcon == NULL) {
381                 fprintf(stderr, "evhttp_connection_base_bufferevent_new() failed\n");
382                 return 1;
383         }
384
385         if (retries > 0) {
386                 evhttp_connection_set_retries(evcon, retries);
387         }
388
389         // Fire off the request
390         req = evhttp_request_new(http_request_done, bev);
391         if (req == NULL) {
392                 fprintf(stderr, "evhttp_request_new() failed\n");
393                 return 1;
394         }
395
396         output_headers = evhttp_request_get_output_headers(req);
397         evhttp_add_header(output_headers, "Host", host);
398         evhttp_add_header(output_headers, "Connection", "close");
399
400         if (data_file) {
401                 /* NOTE: In production code, you'd probably want to use
402                  * evbuffer_add_file() or evbuffer_add_file_segment(), to
403                  * avoid needless copying. */
404                 FILE * f = fopen(data_file, "rb");
405                 char buf[1024];
406                 size_t s;
407                 size_t bytes = 0;
408
409                 if (!f) {
410                         syntax();
411                 }
412
413                 output_buffer = evhttp_request_get_output_buffer(req);
414                 while ((s = fread(buf, 1, sizeof(buf), f)) > 0) {
415                         evbuffer_add(output_buffer, buf, s);
416                         bytes += s;
417                 }
418                 evutil_snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long)bytes);
419                 evhttp_add_header(output_headers, "Content-Length", buf);
420                 fclose(f);
421         }
422
423         r = evhttp_make_request(evcon, req, data_file ? EVHTTP_REQ_POST : EVHTTP_REQ_GET, uri);
424         if (r != 0) {
425                 fprintf(stderr, "evhttp_make_request() failed\n");
426                 return 1;
427         }
428
429         event_base_dispatch(base);
430
431         evhttp_connection_free(evcon);
432         event_base_free(base);
433
434 #ifdef _WIN32
435         WSACleanup();
436 #endif
437
438         return 0;
439 }