]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sample/https-client.c
Import libevent 2.1.18
[FreeBSD/FreeBSD.git] / 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] [-timeout sec] [-crt crt]\n", stderr);
100         fputs("Example:\n", stderr);
101         fputs("   https-client -url https://ip.appspot.com/\n", stderr);
102 }
103
104 static void
105 err(const char *msg)
106 {
107         fputs(msg, stderr);
108 }
109
110 static void
111 err_openssl(const char *func)
112 {
113         fprintf (stderr, "%s failed:\n", func);
114
115         /* This is the OpenSSL function that prints the contents of the
116          * error stack to the specified file handle. */
117         ERR_print_errors_fp (stderr);
118
119         exit(1);
120 }
121
122 #ifndef _WIN32
123 /* See http://archives.seul.org/libevent/users/Jan-2013/msg00039.html */
124 static int cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg)
125 {
126         char cert_str[256];
127         const char *host = (const char *) arg;
128         const char *res_str = "X509_verify_cert failed";
129         HostnameValidationResult res = Error;
130
131         /* This is the function that OpenSSL would call if we hadn't called
132          * SSL_CTX_set_cert_verify_callback().  Therefore, we are "wrapping"
133          * the default functionality, rather than replacing it. */
134         int ok_so_far = 0;
135
136         X509 *server_cert = NULL;
137
138         if (ignore_cert) {
139                 return 1;
140         }
141
142         ok_so_far = X509_verify_cert(x509_ctx);
143
144         server_cert = X509_STORE_CTX_get_current_cert(x509_ctx);
145
146         if (ok_so_far) {
147                 res = validate_hostname(host, server_cert);
148
149                 switch (res) {
150                 case MatchFound:
151                         res_str = "MatchFound";
152                         break;
153                 case MatchNotFound:
154                         res_str = "MatchNotFound";
155                         break;
156                 case NoSANPresent:
157                         res_str = "NoSANPresent";
158                         break;
159                 case MalformedCertificate:
160                         res_str = "MalformedCertificate";
161                         break;
162                 case Error:
163                         res_str = "Error";
164                         break;
165                 default:
166                         res_str = "WTF!";
167                         break;
168                 }
169         }
170
171         X509_NAME_oneline(X509_get_subject_name (server_cert),
172                           cert_str, sizeof (cert_str));
173
174         if (res == MatchFound) {
175                 printf("https server '%s' has this certificate, "
176                        "which looks good to me:\n%s\n",
177                        host, cert_str);
178                 return 1;
179         } else {
180                 printf("Got '%s' for hostname '%s' and certificate:\n%s\n",
181                        res_str, host, cert_str);
182                 return 0;
183         }
184 }
185 #endif
186
187 int
188 main(int argc, char **argv)
189 {
190         int r;
191
192         struct evhttp_uri *http_uri = NULL;
193         const char *url = NULL, *data_file = NULL;
194         const char *crt = "/etc/ssl/certs/ca-certificates.crt";
195         const char *scheme, *host, *path, *query;
196         char uri[256];
197         int port;
198         int retries = 0;
199         int timeout = -1;
200
201         SSL_CTX *ssl_ctx = NULL;
202         SSL *ssl = NULL;
203         struct bufferevent *bev;
204         struct evhttp_connection *evcon = NULL;
205         struct evhttp_request *req;
206         struct evkeyvalq *output_headers;
207         struct evbuffer *output_buffer;
208
209         int i;
210         int ret = 0;
211         enum { HTTP, HTTPS } type = HTTP;
212
213         for (i = 1; i < argc; i++) {
214                 if (!strcmp("-url", argv[i])) {
215                         if (i < argc - 1) {
216                                 url = argv[i + 1];
217                         } else {
218                                 syntax();
219                                 goto error;
220                         }
221                 } else if (!strcmp("-crt", argv[i])) {
222                         if (i < argc - 1) {
223                                 crt = argv[i + 1];
224                         } else {
225                                 syntax();
226                                 goto error;
227                         }
228                 } else if (!strcmp("-ignore-cert", argv[i])) {
229                         ignore_cert = 1;
230                 } else if (!strcmp("-data", argv[i])) {
231                         if (i < argc - 1) {
232                                 data_file = argv[i + 1];
233                         } else {
234                                 syntax();
235                                 goto error;
236                         }
237                 } else if (!strcmp("-retries", argv[i])) {
238                         if (i < argc - 1) {
239                                 retries = atoi(argv[i + 1]);
240                         } else {
241                                 syntax();
242                                 goto error;
243                         }
244                 } else if (!strcmp("-timeout", argv[i])) {
245                         if (i < argc - 1) {
246                                 timeout = atoi(argv[i + 1]);
247                         } else {
248                                 syntax();
249                                 goto error;
250                         }
251                 } else if (!strcmp("-help", argv[i])) {
252                         syntax();
253                         goto error;
254                 }
255         }
256
257         if (!url) {
258                 syntax();
259                 goto error;
260         }
261
262 #ifdef _WIN32
263         {
264                 WORD wVersionRequested;
265                 WSADATA wsaData;
266                 int err;
267
268                 wVersionRequested = MAKEWORD(2, 2);
269
270                 err = WSAStartup(wVersionRequested, &wsaData);
271                 if (err != 0) {
272                         printf("WSAStartup failed with error: %d\n", err);
273                         goto error;
274                 }
275         }
276 #endif // _WIN32
277
278         http_uri = evhttp_uri_parse(url);
279         if (http_uri == NULL) {
280                 err("malformed url");
281                 goto error;
282         }
283
284         scheme = evhttp_uri_get_scheme(http_uri);
285         if (scheme == NULL || (strcasecmp(scheme, "https") != 0 &&
286                                strcasecmp(scheme, "http") != 0)) {
287                 err("url must be http or https");
288                 goto error;
289         }
290
291         host = evhttp_uri_get_host(http_uri);
292         if (host == NULL) {
293                 err("url must have a host");
294                 goto error;
295         }
296
297         port = evhttp_uri_get_port(http_uri);
298         if (port == -1) {
299                 port = (strcasecmp(scheme, "http") == 0) ? 80 : 443;
300         }
301
302         path = evhttp_uri_get_path(http_uri);
303         if (strlen(path) == 0) {
304                 path = "/";
305         }
306
307         query = evhttp_uri_get_query(http_uri);
308         if (query == NULL) {
309                 snprintf(uri, sizeof(uri) - 1, "%s", path);
310         } else {
311                 snprintf(uri, sizeof(uri) - 1, "%s?%s", path, query);
312         }
313         uri[sizeof(uri) - 1] = '\0';
314
315 #if OPENSSL_VERSION_NUMBER < 0x10100000L
316         // Initialize OpenSSL
317         SSL_library_init();
318         ERR_load_crypto_strings();
319         SSL_load_error_strings();
320         OpenSSL_add_all_algorithms();
321 #endif
322
323         /* This isn't strictly necessary... OpenSSL performs RAND_poll
324          * automatically on first use of random number generator. */
325         r = RAND_poll();
326         if (r == 0) {
327                 err_openssl("RAND_poll");
328                 goto error;
329         }
330
331         /* Create a new OpenSSL context */
332         ssl_ctx = SSL_CTX_new(SSLv23_method());
333         if (!ssl_ctx) {
334                 err_openssl("SSL_CTX_new");
335                 goto error;
336         }
337
338 #ifndef _WIN32
339         /* TODO: Add certificate loading on Windows as well */
340
341         /* Attempt to use the system's trusted root certificates.
342          * (This path is only valid for Debian-based systems.) */
343         if (1 != SSL_CTX_load_verify_locations(ssl_ctx, crt, NULL)) {
344                 err_openssl("SSL_CTX_load_verify_locations");
345                 goto error;
346         }
347         /* Ask OpenSSL to verify the server certificate.  Note that this
348          * does NOT include verifying that the hostname is correct.
349          * So, by itself, this means anyone with any legitimate
350          * CA-issued certificate for any website, can impersonate any
351          * other website in the world.  This is not good.  See "The
352          * Most Dangerous Code in the World" article at
353          * https://crypto.stanford.edu/~dabo/pubs/abstracts/ssl-client-bugs.html
354          */
355         SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
356         /* This is how we solve the problem mentioned in the previous
357          * comment.  We "wrap" OpenSSL's validation routine in our
358          * own routine, which also validates the hostname by calling
359          * the code provided by iSECPartners.  Note that even though
360          * the "Everything You've Always Wanted to Know About
361          * Certificate Validation With OpenSSL (But Were Afraid to
362          * Ask)" paper from iSECPartners says very explicitly not to
363          * call SSL_CTX_set_cert_verify_callback (at the bottom of
364          * page 2), what we're doing here is safe because our
365          * cert_verify_callback() calls X509_verify_cert(), which is
366          * OpenSSL's built-in routine which would have been called if
367          * we hadn't set the callback.  Therefore, we're just
368          * "wrapping" OpenSSL's routine, not replacing it. */
369         SSL_CTX_set_cert_verify_callback(ssl_ctx, cert_verify_callback,
370                                           (void *) host);
371 #else // _WIN32
372         (void)crt;
373 #endif // _WIN32
374
375         // Create event base
376         base = event_base_new();
377         if (!base) {
378                 perror("event_base_new()");
379                 goto error;
380         }
381
382         // Create OpenSSL bufferevent and stack evhttp on top of it
383         ssl = SSL_new(ssl_ctx);
384         if (ssl == NULL) {
385                 err_openssl("SSL_new()");
386                 goto error;
387         }
388
389         #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
390         // Set hostname for SNI extension
391         SSL_set_tlsext_host_name(ssl, host);
392         #endif
393
394         if (strcasecmp(scheme, "http") == 0) {
395                 bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
396         } else {
397                 type = HTTPS;
398                 bev = bufferevent_openssl_socket_new(base, -1, ssl,
399                         BUFFEREVENT_SSL_CONNECTING,
400                         BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
401         }
402
403         if (bev == NULL) {
404                 fprintf(stderr, "bufferevent_openssl_socket_new() failed\n");
405                 goto error;
406         }
407
408         bufferevent_openssl_set_allow_dirty_shutdown(bev, 1);
409
410         // For simplicity, we let DNS resolution block. Everything else should be
411         // asynchronous though.
412         evcon = evhttp_connection_base_bufferevent_new(base, NULL, bev,
413                 host, port);
414         if (evcon == NULL) {
415                 fprintf(stderr, "evhttp_connection_base_bufferevent_new() failed\n");
416                 goto error;
417         }
418
419         if (retries > 0) {
420                 evhttp_connection_set_retries(evcon, retries);
421         }
422         if (timeout >= 0) {
423                 evhttp_connection_set_timeout(evcon, timeout);
424         }
425
426         // Fire off the request
427         req = evhttp_request_new(http_request_done, bev);
428         if (req == NULL) {
429                 fprintf(stderr, "evhttp_request_new() failed\n");
430                 goto error;
431         }
432
433         output_headers = evhttp_request_get_output_headers(req);
434         evhttp_add_header(output_headers, "Host", host);
435         evhttp_add_header(output_headers, "Connection", "close");
436
437         if (data_file) {
438                 /* NOTE: In production code, you'd probably want to use
439                  * evbuffer_add_file() or evbuffer_add_file_segment(), to
440                  * avoid needless copying. */
441                 FILE * f = fopen(data_file, "rb");
442                 char buf[1024];
443                 size_t s;
444                 size_t bytes = 0;
445
446                 if (!f) {
447                         syntax();
448                         goto error;
449                 }
450
451                 output_buffer = evhttp_request_get_output_buffer(req);
452                 while ((s = fread(buf, 1, sizeof(buf), f)) > 0) {
453                         evbuffer_add(output_buffer, buf, s);
454                         bytes += s;
455                 }
456                 evutil_snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long)bytes);
457                 evhttp_add_header(output_headers, "Content-Length", buf);
458                 fclose(f);
459         }
460
461         r = evhttp_make_request(evcon, req, data_file ? EVHTTP_REQ_POST : EVHTTP_REQ_GET, uri);
462         if (r != 0) {
463                 fprintf(stderr, "evhttp_make_request() failed\n");
464                 goto error;
465         }
466
467         event_base_dispatch(base);
468         goto cleanup;
469
470 error:
471         ret = 1;
472 cleanup:
473         if (evcon)
474                 evhttp_connection_free(evcon);
475         if (http_uri)
476                 evhttp_uri_free(http_uri);
477         event_base_free(base);
478
479         if (ssl_ctx)
480                 SSL_CTX_free(ssl_ctx);
481         if (type == HTTP && ssl)
482                 SSL_free(ssl);
483 #if OPENSSL_VERSION_NUMBER < 0x10100000L
484         EVP_cleanup();
485         ERR_free_strings();
486
487 #ifdef EVENT__HAVE_ERR_REMOVE_THREAD_STATE
488         ERR_remove_thread_state(NULL);
489 #else
490         ERR_remove_state(0);
491 #endif
492         CRYPTO_cleanup_all_ex_data();
493
494         sk_SSL_COMP_free(SSL_COMP_get_compression_methods());
495 #endif /*OPENSSL_VERSION_NUMBER < 0x10100000L */
496
497 #ifdef _WIN32
498         WSACleanup();
499 #endif
500
501         return ret;
502 }