Imported Upstream version 2.1.10
[platform/upstream/libevent.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 int ignore_cert = 0;
49
50 static void
51 http_request_done(struct evhttp_request *req, void *ctx)
52 {
53         char buffer[256];
54         int nread;
55
56         if (req == NULL) {
57                 /* If req is NULL, it means an error occurred, but
58                  * sadly we are mostly left guessing what the error
59                  * might have been.  We'll do our best... */
60                 struct bufferevent *bev = (struct bufferevent *) ctx;
61                 unsigned long oslerr;
62                 int printed_err = 0;
63                 int errcode = EVUTIL_SOCKET_ERROR();
64                 fprintf(stderr, "some request failed - no idea which one though!\n");
65                 /* Print out the OpenSSL error queue that libevent
66                  * squirreled away for us, if any. */
67                 while ((oslerr = bufferevent_get_openssl_error(bev))) {
68                         ERR_error_string_n(oslerr, buffer, sizeof(buffer));
69                         fprintf(stderr, "%s\n", buffer);
70                         printed_err = 1;
71                 }
72                 /* If the OpenSSL error queue was empty, maybe it was a
73                  * socket error; let's try printing that. */
74                 if (! printed_err)
75                         fprintf(stderr, "socket error = %s (%d)\n",
76                                 evutil_socket_error_to_string(errcode),
77                                 errcode);
78                 return;
79         }
80
81         fprintf(stderr, "Response line: %d %s\n",
82             evhttp_request_get_response_code(req),
83             evhttp_request_get_response_code_line(req));
84
85         while ((nread = evbuffer_remove(evhttp_request_get_input_buffer(req),
86                     buffer, sizeof(buffer)))
87                > 0) {
88                 /* These are just arbitrary chunks of 256 bytes.
89                  * They are not lines, so we can't treat them as such. */
90                 fwrite(buffer, nread, 1, stdout);
91         }
92 }
93
94 static void
95 syntax(void)
96 {
97         fputs("Syntax:\n", stderr);
98         fputs("   https-client -url <https-url> [-data data-file.bin] [-ignore-cert] [-retries num] [-timeout sec] [-crt crt]\n", stderr);
99         fputs("Example:\n", stderr);
100         fputs("   https-client -url https://ip.appspot.com/\n", stderr);
101 }
102
103 static void
104 err(const char *msg)
105 {
106         fputs(msg, stderr);
107 }
108
109 static void
110 err_openssl(const char *func)
111 {
112         fprintf (stderr, "%s failed:\n", func);
113
114         /* This is the OpenSSL function that prints the contents of the
115          * error stack to the specified file handle. */
116         ERR_print_errors_fp (stderr);
117
118         exit(1);
119 }
120
121 #ifndef _WIN32
122 /* See http://archives.seul.org/libevent/users/Jan-2013/msg00039.html */
123 static int cert_verify_callback(X509_STORE_CTX *x509_ctx, void *arg)
124 {
125         char cert_str[256];
126         const char *host = (const char *) arg;
127         const char *res_str = "X509_verify_cert failed";
128         HostnameValidationResult res = Error;
129
130         /* This is the function that OpenSSL would call if we hadn't called
131          * SSL_CTX_set_cert_verify_callback().  Therefore, we are "wrapping"
132          * the default functionality, rather than replacing it. */
133         int ok_so_far = 0;
134
135         X509 *server_cert = NULL;
136
137         if (ignore_cert) {
138                 return 1;
139         }
140
141         ok_so_far = X509_verify_cert(x509_ctx);
142
143         server_cert = X509_STORE_CTX_get_current_cert(x509_ctx);
144
145         if (ok_so_far) {
146                 res = validate_hostname(host, server_cert);
147
148                 switch (res) {
149                 case MatchFound:
150                         res_str = "MatchFound";
151                         break;
152                 case MatchNotFound:
153                         res_str = "MatchNotFound";
154                         break;
155                 case NoSANPresent:
156                         res_str = "NoSANPresent";
157                         break;
158                 case MalformedCertificate:
159                         res_str = "MalformedCertificate";
160                         break;
161                 case Error:
162                         res_str = "Error";
163                         break;
164                 default:
165                         res_str = "WTF!";
166                         break;
167                 }
168         }
169
170         X509_NAME_oneline(X509_get_subject_name (server_cert),
171                           cert_str, sizeof (cert_str));
172
173         if (res == MatchFound) {
174                 printf("https server '%s' has this certificate, "
175                        "which looks good to me:\n%s\n",
176                        host, cert_str);
177                 return 1;
178         } else {
179                 printf("Got '%s' for hostname '%s' and certificate:\n%s\n",
180                        res_str, host, cert_str);
181                 return 0;
182         }
183 }
184 #endif
185
186 int
187 main(int argc, char **argv)
188 {
189         int r;
190         struct event_base *base = NULL;
191         struct evhttp_uri *http_uri = NULL;
192         const char *url = NULL, *data_file = NULL;
193         const char *crt = NULL;
194         const char *scheme, *host, *path, *query;
195         char uri[256];
196         int port;
197         int retries = 0;
198         int timeout = -1;
199
200         SSL_CTX *ssl_ctx = NULL;
201         SSL *ssl = NULL;
202         struct bufferevent *bev;
203         struct evhttp_connection *evcon = NULL;
204         struct evhttp_request *req;
205         struct evkeyvalq *output_headers;
206         struct evbuffer *output_buffer;
207
208         int i;
209         int ret = 0;
210         enum { HTTP, HTTPS } type = HTTP;
211
212         for (i = 1; i < argc; i++) {
213                 if (!strcmp("-url", argv[i])) {
214                         if (i < argc - 1) {
215                                 url = argv[i + 1];
216                         } else {
217                                 syntax();
218                                 goto error;
219                         }
220                 } else if (!strcmp("-crt", argv[i])) {
221                         if (i < argc - 1) {
222                                 crt = argv[i + 1];
223                         } else {
224                                 syntax();
225                                 goto error;
226                         }
227                 } else if (!strcmp("-ignore-cert", argv[i])) {
228                         ignore_cert = 1;
229                 } else if (!strcmp("-data", argv[i])) {
230                         if (i < argc - 1) {
231                                 data_file = argv[i + 1];
232                         } else {
233                                 syntax();
234                                 goto error;
235                         }
236                 } else if (!strcmp("-retries", argv[i])) {
237                         if (i < argc - 1) {
238                                 retries = atoi(argv[i + 1]);
239                         } else {
240                                 syntax();
241                                 goto error;
242                         }
243                 } else if (!strcmp("-timeout", argv[i])) {
244                         if (i < argc - 1) {
245                                 timeout = atoi(argv[i + 1]);
246                         } else {
247                                 syntax();
248                                 goto error;
249                         }
250                 } else if (!strcmp("-help", argv[i])) {
251                         syntax();
252                         goto error;
253                 }
254         }
255
256         if (!url) {
257                 syntax();
258                 goto error;
259         }
260
261 #ifdef _WIN32
262         {
263                 WORD wVersionRequested;
264                 WSADATA wsaData;
265                 int err;
266
267                 wVersionRequested = MAKEWORD(2, 2);
268
269                 err = WSAStartup(wVersionRequested, &wsaData);
270                 if (err != 0) {
271                         printf("WSAStartup failed with error: %d\n", err);
272                         goto error;
273                 }
274         }
275 #endif // _WIN32
276
277         http_uri = evhttp_uri_parse(url);
278         if (http_uri == NULL) {
279                 err("malformed url");
280                 goto error;
281         }
282
283         scheme = evhttp_uri_get_scheme(http_uri);
284         if (scheme == NULL || (strcasecmp(scheme, "https") != 0 &&
285                                strcasecmp(scheme, "http") != 0)) {
286                 err("url must be http or https");
287                 goto error;
288         }
289
290         host = evhttp_uri_get_host(http_uri);
291         if (host == NULL) {
292                 err("url must have a host");
293                 goto error;
294         }
295
296         port = evhttp_uri_get_port(http_uri);
297         if (port == -1) {
298                 port = (strcasecmp(scheme, "http") == 0) ? 80 : 443;
299         }
300
301         path = evhttp_uri_get_path(http_uri);
302         if (strlen(path) == 0) {
303                 path = "/";
304         }
305
306         query = evhttp_uri_get_query(http_uri);
307         if (query == NULL) {
308                 snprintf(uri, sizeof(uri) - 1, "%s", path);
309         } else {
310                 snprintf(uri, sizeof(uri) - 1, "%s?%s", path, query);
311         }
312         uri[sizeof(uri) - 1] = '\0';
313
314 #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
315         (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
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         if (crt == NULL) {
342                 X509_STORE *store;
343                 /* Attempt to use the system's trusted root certificates. */
344                 store = SSL_CTX_get_cert_store(ssl_ctx);
345                 if (X509_STORE_set_default_paths(store) != 1) {
346                         err_openssl("X509_STORE_set_default_paths");
347                         goto error;
348                 }
349         } else {
350                 if (SSL_CTX_load_verify_locations(ssl_ctx, crt, NULL) != 1) {
351                         err_openssl("SSL_CTX_load_verify_locations");
352                         goto error;
353                 }
354         }
355         /* Ask OpenSSL to verify the server certificate.  Note that this
356          * does NOT include verifying that the hostname is correct.
357          * So, by itself, this means anyone with any legitimate
358          * CA-issued certificate for any website, can impersonate any
359          * other website in the world.  This is not good.  See "The
360          * Most Dangerous Code in the World" article at
361          * https://crypto.stanford.edu/~dabo/pubs/abstracts/ssl-client-bugs.html
362          */
363         SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
364         /* This is how we solve the problem mentioned in the previous
365          * comment.  We "wrap" OpenSSL's validation routine in our
366          * own routine, which also validates the hostname by calling
367          * the code provided by iSECPartners.  Note that even though
368          * the "Everything You've Always Wanted to Know About
369          * Certificate Validation With OpenSSL (But Were Afraid to
370          * Ask)" paper from iSECPartners says very explicitly not to
371          * call SSL_CTX_set_cert_verify_callback (at the bottom of
372          * page 2), what we're doing here is safe because our
373          * cert_verify_callback() calls X509_verify_cert(), which is
374          * OpenSSL's built-in routine which would have been called if
375          * we hadn't set the callback.  Therefore, we're just
376          * "wrapping" OpenSSL's routine, not replacing it. */
377         SSL_CTX_set_cert_verify_callback(ssl_ctx, cert_verify_callback,
378                                           (void *) host);
379 #else // _WIN32
380         (void)crt;
381 #endif // _WIN32
382
383         // Create event base
384         base = event_base_new();
385         if (!base) {
386                 perror("event_base_new()");
387                 goto error;
388         }
389
390         // Create OpenSSL bufferevent and stack evhttp on top of it
391         ssl = SSL_new(ssl_ctx);
392         if (ssl == NULL) {
393                 err_openssl("SSL_new()");
394                 goto error;
395         }
396
397         #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
398         // Set hostname for SNI extension
399         SSL_set_tlsext_host_name(ssl, host);
400         #endif
401
402         if (strcasecmp(scheme, "http") == 0) {
403                 bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
404         } else {
405                 type = HTTPS;
406                 bev = bufferevent_openssl_socket_new(base, -1, ssl,
407                         BUFFEREVENT_SSL_CONNECTING,
408                         BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
409         }
410
411         if (bev == NULL) {
412                 fprintf(stderr, "bufferevent_openssl_socket_new() failed\n");
413                 goto error;
414         }
415
416         bufferevent_openssl_set_allow_dirty_shutdown(bev, 1);
417
418         // For simplicity, we let DNS resolution block. Everything else should be
419         // asynchronous though.
420         evcon = evhttp_connection_base_bufferevent_new(base, NULL, bev,
421                 host, port);
422         if (evcon == NULL) {
423                 fprintf(stderr, "evhttp_connection_base_bufferevent_new() failed\n");
424                 goto error;
425         }
426
427         if (retries > 0) {
428                 evhttp_connection_set_retries(evcon, retries);
429         }
430         if (timeout >= 0) {
431                 evhttp_connection_set_timeout(evcon, timeout);
432         }
433
434         // Fire off the request
435         req = evhttp_request_new(http_request_done, bev);
436         if (req == NULL) {
437                 fprintf(stderr, "evhttp_request_new() failed\n");
438                 goto error;
439         }
440
441         output_headers = evhttp_request_get_output_headers(req);
442         evhttp_add_header(output_headers, "Host", host);
443         evhttp_add_header(output_headers, "Connection", "close");
444
445         if (data_file) {
446                 /* NOTE: In production code, you'd probably want to use
447                  * evbuffer_add_file() or evbuffer_add_file_segment(), to
448                  * avoid needless copying. */
449                 FILE * f = fopen(data_file, "rb");
450                 char buf[1024];
451                 size_t s;
452                 size_t bytes = 0;
453
454                 if (!f) {
455                         syntax();
456                         goto error;
457                 }
458
459                 output_buffer = evhttp_request_get_output_buffer(req);
460                 while ((s = fread(buf, 1, sizeof(buf), f)) > 0) {
461                         evbuffer_add(output_buffer, buf, s);
462                         bytes += s;
463                 }
464                 evutil_snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long)bytes);
465                 evhttp_add_header(output_headers, "Content-Length", buf);
466                 fclose(f);
467         }
468
469         r = evhttp_make_request(evcon, req, data_file ? EVHTTP_REQ_POST : EVHTTP_REQ_GET, uri);
470         if (r != 0) {
471                 fprintf(stderr, "evhttp_make_request() failed\n");
472                 goto error;
473         }
474
475         event_base_dispatch(base);
476         goto cleanup;
477
478 error:
479         ret = 1;
480 cleanup:
481         if (evcon)
482                 evhttp_connection_free(evcon);
483         if (http_uri)
484                 evhttp_uri_free(http_uri);
485         if (base)
486                 event_base_free(base);
487
488         if (ssl_ctx)
489                 SSL_CTX_free(ssl_ctx);
490         if (type == HTTP && ssl)
491                 SSL_free(ssl);
492 #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
493         (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
494         EVP_cleanup();
495         ERR_free_strings();
496
497 #if OPENSSL_VERSION_NUMBER < 0x10000000L
498         ERR_remove_state(0);
499 #else
500         ERR_remove_thread_state(NULL);
501 #endif
502
503         CRYPTO_cleanup_all_ex_data();
504
505         sk_SSL_COMP_free(SSL_COMP_get_compression_methods());
506 #endif /* (OPENSSL_VERSION_NUMBER < 0x10100000L) || \
507         (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) */
508
509 #ifdef _WIN32
510         WSACleanup();
511 #endif
512
513         return ret;
514 }