libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit 9889fd9eeec2c8d40f6a627720fb1afeac044ab0
parent 00a9277f498260289d89f0de6783829b6391394b
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 21 Dec 2013 16:28:54 +0000

add support for SNI

Diffstat:
MChangeLog | 2+-
Mdoc/chapters/tlsauthentication.inc | 247+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mdoc/libmicrohttpd-tutorial.texi | 18+++++++++---------
Mdoc/libmicrohttpd.texi | 16++++++++++++++++
Msrc/examples/demo.c | 106++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/include/microhttpd.h | 19++++++++++++++++++-
Msrc/microhttpd/daemon.c | 24++++++++++++++++++++++++
Msrc/microhttpd/internal.h | 11+++++++++++
Msrc/testcurl/https/Makefile.am | 10++++++++++
Asrc/testcurl/https/host1.crt | 15+++++++++++++++
Asrc/testcurl/https/host1.key | 15+++++++++++++++
Asrc/testcurl/https/host2.crt | 15+++++++++++++++
Asrc/testcurl/https/host2.key | 15+++++++++++++++
Asrc/testcurl/https/test_https_sni.c | 289++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14 files changed, 700 insertions(+), 102 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,7 +1,7 @@ Sat Dec 21 17:26:08 CET 2013 Fixed an issue with a missing argument in the postexample. Fixed issue with bogus offset increment involving sendfile - on GNU/Linux. + on GNU/Linux. Adding support for SNI. -CG Mon Dec 9 21:41:57 CET 2013 Fix for per-worker daemon pipes enabled with diff --git a/doc/chapters/tlsauthentication.inc b/doc/chapters/tlsauthentication.inc @@ -1,6 +1,6 @@ We left the basic authentication chapter with the unsatisfactory conclusion that any traffic, including the credentials, could be intercepted by anyone between -the browser client and the server. Protecting the data while it is sent over +the browser client and the server. Protecting the data while it is sent over unsecured lines will be the goal of this chapter. Since version 0.4, the @emph{MHD} library includes support for encrypting the @@ -23,7 +23,7 @@ this tutorial, we will be content with a 1024 bit key: In addition to the key, a certificate describing the server in human readable tokens is also needed. This certificate will be attested with our aforementioned key. In this way, -we obtain a self-signed certificate, valid for one year. +we obtain a self-signed certificate, valid for one year. @verbatim > openssl req -days 365 -out server.pem -new -x509 -key server.key @@ -38,7 +38,7 @@ any visitor can make sure the server's identity is real. Whether the server's certificate is signed by us or a third party, once it has been accepted by the client, both sides will be communicating over encrypted channels. From this point on, -it is the client's turn to authenticate itself. But this has already been implemented in the basic +it is the client's turn to authenticate itself. But this has already been implemented in the basic authentication scheme. @@ -65,12 +65,12 @@ main () @end verbatim @noindent -and then we point the @emph{MHD} daemon to it upon initalization. +and then we point the @emph{MHD} daemon to it upon initalization. @verbatim - daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL, + daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL, PORT, NULL, NULL, - &answer_to_connection, NULL, + &answer_to_connection, NULL, MHD_OPTION_HTTPS_MEM_KEY, key_pem, MHD_OPTION_HTTPS_MEM_CERT, cert_pem, MHD_OPTION_END); @@ -78,10 +78,10 @@ and then we point the @emph{MHD} daemon to it upon initalization. if (NULL == daemon) { printf ("%s\n", cert_pem); - + free (key_pem); free (cert_pem); - + return 1; } @end verbatim @@ -96,7 +96,7 @@ The rest consists of little new besides some additional memory cleanups. MHD_stop_daemon (daemon); free (key_pem); free (cert_pem); - + return 0; } @end verbatim @@ -110,18 +110,18 @@ The rather unexciting file loader can be found in the complete example @code{tls @itemize @bullet @item While the standard @emph{HTTP} port is 80, it is 443 for @emph{HTTPS}. The common internet browsers assume -standard @emph{HTTP} if they are asked to access other ports than these. Therefore, you will have to type +standard @emph{HTTP} if they are asked to access other ports than these. Therefore, you will have to type @code{https://localhost:8888} explicitly when you test the example, or the browser will not know how to handle the answer properly. @item The remaining weak point is the question how the server will be trusted initially. Either a @emph{CA} signs the -certificate or the client obtains the key over secure means. Anyway, the clients have to be aware (or configured) +certificate or the client obtains the key over secure means. Anyway, the clients have to be aware (or configured) that they should not accept certificates of unknown origin. @item The introduced method of certificates makes it mandatory to set an expiration date---making it less feasible to -hardcode certificates in embedded devices. +hardcode certificates in embedded devices. @item The cryptographic facilities consume memory space and computing time. For this reason, websites usually consists @@ -135,12 +135,12 @@ both of uncritically @emph{HTTP} parts and secured @emph{HTTPS}. You can also use MHD to authenticate the client via SSL/TLS certificates (as an alternative to using the password-based Basic or Digest authentication). To do this, you will need to link your application against @emph{gnutls}. -Next, when you start the MHD daemon, you must specify the root CA that you're +Next, when you start the MHD daemon, you must specify the root CA that you're willing to trust: @verbatim - daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL, + daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL, PORT, NULL, NULL, - &answer_to_connection, NULL, + &answer_to_connection, NULL, MHD_OPTION_HTTPS_MEM_KEY, key_pem, MHD_OPTION_HTTPS_MEM_CERT, cert_pem, MHD_OPTION_HTTPS_MEM_TRUST, root_ca_pem, @@ -159,7 +159,7 @@ obtain the raw GnuTLS session handle from @emph{MHD} using gnutls_session_t tls_session; union MHD_ConnectionInfo *ci; -ci = MHD_get_connection_info (connection, +ci = MHD_get_connection_info (connection, MHD_CONNECTION_INFO_GNUTLS_SESSION); tls_session = ci->tls_session; @end verbatim @@ -172,31 +172,31 @@ You can then extract the client certificate: * * @param tls_session the TLS session * @return NULL if no valid client certificate could be found, a pointer - * to the certificate if found + * to the certificate if found */ static gnutls_x509_crt_t -get_client_certificate (gnutls_session_t tls_session) +get_client_certificate (gnutls_session_t tls_session) { unsigned int listsize; const gnutls_datum_t * pcert; gnutls_certificate_status_t client_cert_status; gnutls_x509_crt_t client_cert; - if (tls_session == NULL) + if (tls_session == NULL) return NULL; if (gnutls_certificate_verify_peers2(tls_session, - &client_cert_status)) + &client_cert_status)) return NULL; - pcert = gnutls_certificate_get_peers(tls_session, + pcert = gnutls_certificate_get_peers(tls_session, &listsize); - if ( (pcert == NULL) || - (listsize == 0)) + if ( (pcert == NULL) || + (listsize == 0)) { fprintf (stderr, "Failed to retrieve client certificate chain\n"); return NULL; - } - if (gnutls_x509_crt_init(&client_cert)) + } + if (gnutls_x509_crt_init(&client_cert)) { fprintf (stderr, "Failed to initialize client certificate\n"); @@ -204,15 +204,15 @@ get_client_certificate (gnutls_session_t tls_session) } /* Note that by passing values between 0 and listsize here, you can get access to the CA's certs */ - if (gnutls_x509_crt_import(client_cert, + if (gnutls_x509_crt_import(client_cert, &pcert[0], - GNUTLS_X509_FMT_DER)) + GNUTLS_X509_FMT_DER)) { fprintf (stderr, "Failed to import client certificate\n"); gnutls_x509_crt_deinit(client_cert); return NULL; - } + } return client_cert; } @end verbatim @@ -229,15 +229,15 @@ and alternative names: * to the dn if found */ char * -cert_auth_get_dn(gnutls_x509_crt_c client_cert) +cert_auth_get_dn(gnutls_x509_crt_c client_cert) { char* buf; - size_t lbuf; + size_t lbuf; lbuf = 0; gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf); buf = malloc(lbuf); - if (buf == NULL) + if (buf == NULL) { fprintf (stderr, "Failed to allocate memory for certificate dn\n"); @@ -260,8 +260,8 @@ cert_auth_get_dn(gnutls_x509_crt_c client_cert) */ char * MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert, - int nametype, - unsigned int index) + int nametype, + unsigned int index) { char* buf; size_t lbuf; @@ -271,7 +271,7 @@ MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert, int result; subseq = 0; - for (seq=0;;seq++) + for (seq=0;;seq++) { lbuf = 0; result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, NULL, &lbuf, @@ -280,21 +280,21 @@ MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert, return NULL; if (nametype != (int) type) continue; - if (subseq == index) + if (subseq == index) break; subseq++; } buf = malloc(lbuf); - if (buf == NULL) + if (buf == NULL) { fprintf (stderr, "Failed to allocate memory for certificate alt name\n"); return NULL; } - result = gnutls_x509_crt_get_subject_alt_name2(client_cert, + result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, buf, - &lbuf, + &lbuf, NULL, NULL); if (result != nametype) { @@ -315,3 +315,174 @@ certificate: gnutls_x509_crt_deinit (client_cert); @end verbatim + + +@heading Using TLS Server Name Indication (SNI) + +SNI enables hosting multiple domains under one IP address with TLS. So +SNI is the TLS-equivalent of virtual hosting. To use SNI with MHD, you +need at least GnuTLS 3.0. The main change compared to the simple hosting +of one domain is that you need to provide a callback instead of the key +and certificate. For example, when you start the MHD daemon, you could +do this: +@verbatim + daemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_SSL, + PORT, NULL, NULL, + &answer_to_connection, NULL, + MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback, + MHD_OPTION_END); +@end verbatim +Here, @code{sni_callback} is the name of a function that you will have to +implement to retrieve the X.509 certificate for an incoming connection. +The callback has type @code{gnutls_certificate_retrieve_function2} and +is documented in the GnuTLS API for the @code{gnutls_certificate_set_retrieve_function2} +as follows: + +@deftypefn {Function Pointer} int {*gnutls_certificate_retrieve_function2} (gnutls_session_t, const gnutls_datum_t* req_ca_dn, int nreqs, const gnutls_pk_algorithm_t* pk_algos, int pk_algos_length, gnutls_pcert_st** pcert, unsigned int *pcert_length, gnutls_privkey_t * pkey) + +@table @var +@item req_ca_cert +is only used in X.509 certificates. Contains a list with the CA names that the server considers trusted. Normally we should send a certificate that is signed by one of these CAs. These names are DER encoded. To get a more meaningful value use the function @code{gnutls_x509_rdn_get()}. + +@item pk_algos +contains a list with server’s acceptable signature algorithms. The certificate returned should support the server’s given algorithms. + +@item pcert +should contain a single certificate and public or a list of them. + +@item pcert_length +is the size of the previous list. + +@item pkey +is the private key. +@end table +@end deftypefn + +A possible implementation of this callback would look like this: + +@verbatim +struct Hosts +{ + struct Hosts *next; + const char *hostname; + gnutls_pcert_st pcrt; + gnutls_privkey_t key; +}; + +static struct Hosts *hosts; + +int +sni_callback (gnutls_session_t session, + const gnutls_datum_t* req_ca_dn, + int nreqs, + const gnutls_pk_algorithm_t* pk_algos, + int pk_algos_length, + gnutls_pcert_st** pcert, + unsigned int *pcert_length, + gnutls_privkey_t * pkey) +{ + char name[256]; + size_t name_len; + struct Hosts *host; + unsigned int type; + + name_len = sizeof (name); + if (GNUTLS_E_SUCCESS != + gnutls_server_name_get (session, + name, + &name_len, + &type, + 0 /* index */)) + return -1; + for (host = hosts; NULL != host; host = host->next) + if (0 == strncmp (name, host->hostname, name_len)) + break; + if (NULL == host) + { + fprintf (stderr, + "Need certificate for %.*s\n", + (int) name_len, + name); + return -1; + } + fprintf (stderr, + "Returning certificate for %.*s\n", + (int) name_len, + name); + *pkey = host->key; + *pcert_length = 1; + *pcert = &host->pcrt; + return 0; +} +@end verbatim + +Note that MHD cannot offer passing a closure or any other additional information +to this callback, as the GnuTLS API unfortunately does not permit this at this +point. + +The @code{hosts} list can be initialized by loading the private keys and X.509 +certificats from disk as follows: + +@verbatim +static void +load_keys(const char *hostname, + const char *CERT_FILE, + const char *KEY_FILE) +{ + int ret; + gnutls_datum_t data; + struct Hosts *host; + + host = malloc (sizeof (struct Hosts)); + host->hostname = hostname; + host->next = hosts; + hosts = host; + + ret = gnutls_load_file (CERT_FILE, &data); + if (ret < 0) + { + fprintf (stderr, + "*** Error loading certificate file %s.\n", + CERT_FILE); + exit(1); + } + ret = + gnutls_pcert_import_x509_raw (&host->pcrt, &data, GNUTLS_X509_FMT_PEM, + 0); + if (ret < 0) + { + fprintf(stderr, + "*** Error loading certificate file: %s\n", + gnutls_strerror (ret)); + exit(1); + } + gnutls_free (data.data); + + ret = gnutls_load_file (KEY_FILE, &data); + if (ret < 0) + { + fprintf (stderr, + "*** Error loading key file %s.\n", + KEY_FILE); + exit(1); + } + + gnutls_privkey_init (&host->key); + ret = + gnutls_privkey_import_x509_raw (host->key, + &data, GNUTLS_X509_FMT_PEM, + NULL, 0); + if (ret < 0) + { + fprintf (stderr, + "*** Error loading key file: %s\n", + gnutls_strerror (ret)); + exit(1); + } + gnutls_free (data.data); +} +@end verbatim + +The code above was largely lifted from GnuTLS. You can find other +methods for initializing certificates and keys in the GnuTLS manual +and source code. diff --git a/doc/libmicrohttpd-tutorial.texi b/doc/libmicrohttpd-tutorial.texi @@ -1,10 +1,10 @@ \input texinfo @c -*-texinfo-*- @finalout @setfilename libmicrohttpd-tutorial.info -@set UPDATED 17 July 2012 -@set UPDATED-MONTH July 2012 -@set EDITION 0.9.22 -@set VERSION 0.9.22 +@set UPDATED 17 November 2013 +@set UPDATED-MONTH November 2013 +@set EDITION 0.9.23 +@set VERSION 0.9.23 @settitle A tutorial for GNU libmicrohttpd @c Unify all the indices into concept index. @syncodeindex fn cp @@ -20,11 +20,11 @@ @copying This tutorial documents GNU libmicrohttpd version @value{VERSION}, last -updated @value{UPDATED}. +updated @value{UPDATED}. Copyright (c) 2008 Sebastian Gerhardt. -Copyright (c) 2010, 2011, 2012 Christian Grothoff. +Copyright (c) 2010, 2011, 2012, 2013 Christian Grothoff. @quotation Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 @@ -56,7 +56,7 @@ Free Documentation License". @node Top @top A Tutorial for GNU libmicrohttpd @insertcopying -@end ifnottex +@end ifnottex @menu * Introduction:: @@ -97,8 +97,8 @@ Free Documentation License". @chapter Processing POST data @include chapters/processingpost.inc -@node Improved processing of POST data -@chapter Improved processing of POST data +@node Improved processing of POST data +@chapter Improved processing of POST data @include chapters/largerpost.inc @node Session management diff --git a/doc/libmicrohttpd.texi b/doc/libmicrohttpd.texi @@ -701,6 +701,22 @@ are acceptable for the application. The string is passed unchanged to gnutls_priority_init. If this option is not specified, ``NORMAL'' is used. +@item MHD_OPTION_HTTPS_CERT_CALLBACK +@cindex SSL +@cindex TLS +@cindex SNI +Use a callback to determine which X.509 certificate should be used for +a given HTTPS connection. This option should be followed by a +argument of type "gnutls_certificate_retrieve_function2 *". This +option provides an alternative to MHD_OPTION_HTTPS_MEM_KEY and +MHD_OPTION_HTTPS_MEM_CERT. You must use this version if multiple +domains are to be hosted at the same IP address using TLS's Server +Name Indication (SNI) extension. In this case, the callback is +expected to select the correct certificate based on the SNI +information provided. The callback is expected to access the SNI data +using gnutls_server_name_get(). Using this option requires GnuTLS 3.0 +or higher. + @item MHD_OPTION_DIGEST_AUTH_RANDOM @cindex digest auth @cindex random diff --git a/src/examples/demo.c b/src/examples/demo.c @@ -21,7 +21,7 @@ * @file demo.c * @brief complex demonstration site: create directory index, offer * upload via form and HTTP POST, download with mime type detection - * and error reporting (403, etc.) --- and all of this with + * and error reporting (403, etc.) --- and all of this with * high-performance settings (large buffers, thread pool). * If you want to benchmark MHD, this code should be used to * run tests against. Note that the number of threads may need @@ -222,7 +222,7 @@ struct ResponseDataContext * Response data string. */ char *buf; - + /** * Number of bytes allocated for 'buf'. */ @@ -253,12 +253,12 @@ list_directory (struct ResponseDataContext *rdc, struct dirent *de; if (NULL == (dir = opendir (dirname))) - return MHD_NO; + return MHD_NO; while (NULL != (de = readdir (dir))) { if ('.' == de->d_name[0]) continue; - if (sizeof (fullname) <= + if (sizeof (fullname) <= snprintf (fullname, sizeof (fullname), "%s/%s", dirname, de->d_name)) @@ -278,7 +278,7 @@ list_directory (struct ResponseDataContext *rdc, break; /* out of memory */ rdc->buf = r; } - rdc->off += snprintf (&rdc->buf[rdc->off], + rdc->off += snprintf (&rdc->buf[rdc->off], rdc->buf_len - rdc->off, "<li><a href=\"/%s\">%s</a></li>\n", fullname, @@ -305,11 +305,11 @@ update_directory () char dir_name[128]; struct stat sbuf; - rdc.buf_len = initial_allocation; + rdc.buf_len = initial_allocation; if (NULL == (rdc.buf = malloc (rdc.buf_len))) { update_cached_response (NULL); - return; + return; } rdc.off = snprintf (rdc.buf, rdc.buf_len, "%s", @@ -342,7 +342,7 @@ update_directory () rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off, "<h3>%s</h3>\n", category); - + if (MHD_NO == list_directory (&rdc, dir_name)) { free (rdc.buf); @@ -352,7 +352,7 @@ update_directory () } } /* we ensured always +1k room, filenames are ~256 bytes, - so there is always still enough space for the footer + so there is always still enough space for the footer without need for a final reallocation check. */ rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off, "%s", @@ -427,7 +427,7 @@ do_append (char **ret, { char *buf; size_t old_len; - + if (NULL == *ret) old_len = 0; else @@ -471,8 +471,8 @@ process_upload_data (void *cls, const char *filename, const char *content_type, const char *transfer_encoding, - const char *data, - uint64_t off, + const char *data, + uint64_t off, size_t size) { struct UploadContext *uc = cls; @@ -484,10 +484,10 @@ process_upload_data (void *cls, return do_append (&uc->language, data, size); if (0 != strcmp (key, "upload")) { - fprintf (stderr, + fprintf (stderr, "Ignoring unexpected form value `%s'\n", key); - return MHD_YES; /* ignore */ + return MHD_YES; /* ignore */ } if (NULL == filename) { @@ -497,7 +497,7 @@ process_upload_data (void *cls, if ( (NULL == uc->category) || (NULL == uc->language) ) { - fprintf (stderr, + fprintf (stderr, "Missing form data for upload `%s'\n", filename); uc->response = request_refused_response; @@ -523,8 +523,8 @@ process_upload_data (void *cls, snprintf (fn, sizeof (fn), "%s/%s", uc->language, - uc->category); -#ifdef WINDOWS + uc->category); +#ifdef WINDOWS (void) mkdir (fn); #else (void) mkdir (fn, S_IRWXU); @@ -534,12 +534,12 @@ process_upload_data (void *cls, "%s/%s/%s", uc->language, uc->category, - filename); + filename); for (i=strlen (fn)-1;i>=0;i--) if (! isprint ((int) fn[i])) fn[i] = '_'; - uc->fd = open (fn, - O_CREAT | O_EXCL + uc->fd = open (fn, + O_CREAT | O_EXCL #if O_LARGEFILE | O_LARGEFILE #endif @@ -547,20 +547,20 @@ process_upload_data (void *cls, S_IRUSR | S_IWUSR); if (-1 == uc->fd) { - fprintf (stderr, + fprintf (stderr, "Error opening file `%s' for upload: %s\n", fn, strerror (errno)); uc->response = request_refused_response; return MHD_NO; - } + } uc->filename = strdup (fn); } if ( (0 != size) && - (size != write (uc->fd, data, size)) ) + (size != write (uc->fd, data, size)) ) { /* write failed; likely: disk full */ - fprintf (stderr, + fprintf (stderr, "Error writing to file `%s': %s\n", uc->filename, strerror (errno)); @@ -573,7 +573,7 @@ process_upload_data (void *cls, free (uc->filename); uc->filename = NULL; } - return MHD_NO; + return MHD_NO; } return MHD_YES; } @@ -610,13 +610,13 @@ response_completed_callback (void *cls, (void) close (uc->fd); if (NULL != uc->filename) { - fprintf (stderr, + fprintf (stderr, "Upload of file `%s' failed (incomplete or aborted), removing file.\n", uc->filename); (void) unlink (uc->filename); } } - if (NULL != uc->filename) + if (NULL != uc->filename) free (uc->filename); free (uc); } @@ -624,7 +624,7 @@ response_completed_callback (void *cls, /** * Return the current directory listing. - * + * * @param connection connection to return the directory for * @return MHD_YES on success, MHD_NO on error */ @@ -635,12 +635,12 @@ return_directory_response (struct MHD_Connection *connection) (void) pthread_mutex_lock (&mutex); if (NULL == cached_directory_response) - ret = MHD_queue_response (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, + ret = MHD_queue_response (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, internal_error_response); else - ret = MHD_queue_response (connection, - MHD_HTTP_OK, + ret = MHD_queue_response (connection, + MHD_HTTP_OK, cached_directory_response); (void) pthread_mutex_unlock (&mutex); return ret; @@ -657,7 +657,7 @@ return_directory_response (struct MHD_Connection *connection) * @param version HTTP version * @param upload_data data from upload (PUT/POST) * @param upload_data_size number of bytes in "upload_data" - * @param ptr our context + * @param ptr our context * @return MHD_YES on success, MHD_NO to drop connection */ static int @@ -668,11 +668,11 @@ generate_page (void *cls, const char *version, const char *upload_data, size_t *upload_data_size, void **ptr) -{ +{ struct MHD_Response *response; int ret; int fd; - struct stat buf; + struct stat buf; if (0 != strcmp (url, "/")) { @@ -685,13 +685,13 @@ generate_page (void *cls, return MHD_NO; /* unexpected method (we're not polite...) */ if ( (0 == stat (&url[1], &buf)) && (NULL == strstr (&url[1], "..")) && - ('/' != url[1])) + ('/' != url[1])) fd = open (&url[1], O_RDONLY); else fd = -1; if (-1 == fd) - return MHD_queue_response (connection, - MHD_HTTP_NOT_FOUND, + return MHD_queue_response (connection, + MHD_HTTP_NOT_FOUND, file_not_found_response); /* read beginning of the file to determine mime type */ got = read (fd, file_data, sizeof (file_data)); @@ -701,12 +701,12 @@ generate_page (void *cls, mime = NULL; (void) lseek (fd, 0, SEEK_SET); - if (NULL == (response = MHD_create_response_from_fd (buf.st_size, + if (NULL == (response = MHD_create_response_from_fd (buf.st_size, fd))) { /* internal error (i.e. out of memory) */ (void) close (fd); - return MHD_NO; + return MHD_NO; } /* add mime type if we had one */ @@ -714,8 +714,8 @@ generate_page (void *cls, (void) MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, mime); - ret = MHD_queue_response (connection, - MHD_HTTP_OK, + ret = MHD_queue_response (connection, + MHD_HTTP_OK, response); MHD_destroy_response (response); return ret; @@ -744,11 +744,11 @@ generate_page (void *cls, } *ptr = uc; return MHD_YES; - } + } if (0 != *upload_data_size) { if (NULL == uc->response) - (void) MHD_post_process (uc->pp, + (void) MHD_post_process (uc->pp, upload_data, *upload_data_size); *upload_data_size = 0; @@ -764,8 +764,8 @@ generate_page (void *cls, } if (NULL != uc->response) { - return MHD_queue_response (connection, - MHD_HTTP_FORBIDDEN, + return MHD_queue_response (connection, + MHD_HTTP_FORBIDDEN, uc->response); } else @@ -778,8 +778,8 @@ generate_page (void *cls, return return_directory_response (connection); /* unexpected request, refuse */ - return MHD_queue_response (connection, - MHD_HTTP_FORBIDDEN, + return MHD_queue_response (connection, + MHD_HTTP_FORBIDDEN, request_refused_response); } @@ -837,7 +837,7 @@ main (int argc, char *const *argv) if ( (argc != 2) || (1 != sscanf (argv[1], "%u", &port)) || - (UINT16_MAX < port) ) + (UINT16_MAX < port) ) { fprintf (stderr, "%s PORT\n", argv[0]); @@ -864,14 +864,14 @@ main (int argc, char *const *argv) mark_as_html (internal_error_response); update_directory (); d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG -#if EPOLL_SUPPORT +#if EPOLL_SUPPORT | MHD_USE_EPOLL_LINUX_ONLY #endif , port, - NULL, NULL, - &generate_page, NULL, - MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (256 * 1024), + NULL, NULL, + &generate_page, NULL, + MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (256 * 1024), #if PRODUCTION MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) (64), #endif diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -774,7 +774,24 @@ enum MHD_OPTION * Increment to use for growing the read buffer (followed by a * `size_t`). Must fit within #MHD_OPTION_CONNECTION_MEMORY_LIMIT. */ - MHD_OPTION_CONNECTION_MEMORY_INCREMENT = 21 + MHD_OPTION_CONNECTION_MEMORY_INCREMENT = 21, + + /** + * Use a callback to determine which X.509 certificate should be + * used for a given HTTPS connection. This option should be + * followed by a argument of type `gnutls_certificate_retrieve_function2 *`. + * This option provides an + * alternative to #MHD_OPTION_HTTPS_MEM_KEY, + * #MHD_OPTION_HTTPS_MEM_CERT. You must use this version if + * multiple domains are to be hosted at the same IP address using + * TLS's Server Name Indication (SNI) extension. In this case, + * the callback is expected to select the correct certificate + * based on the SNI information provided. The callback is expected + * to access the SNI data using `gnutls_server_name_get()`. + * Using this option requires GnuTLS 3.0 or higher. + */ + MHD_OPTION_HTTPS_CERT_CALLBACK = 22 + }; diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c @@ -471,6 +471,13 @@ MHD_init_daemon_certificate (struct MHD_Daemon *daemon) gnutls_datum_t key; gnutls_datum_t cert; +#if GNUTLS_VERSION_MAJOR >= 3 + if (NULL != daemon->cert_callback) + { + gnutls_certificate_set_retrieve_function2 (daemon->x509_cred, + daemon->cert_callback); + } +#endif if (NULL != daemon->https_mem_trust) { cert.data = (unsigned char *) daemon->https_mem_trust; @@ -499,6 +506,10 @@ MHD_init_daemon_certificate (struct MHD_Daemon *daemon) &cert, &key, GNUTLS_X509_FMT_PEM); } +#if GNUTLS_VERSION_MAJOR >= 3 + if (NULL != daemon->cert_callback) + return 0; +#endif #if HAVE_MESSAGES MHD_DLOG (daemon, "You need to specify a certificate and key location\n"); #endif @@ -2900,6 +2911,18 @@ parse_options_va (struct MHD_Daemon *daemon, } } break; + case MHD_OPTION_HTTPS_CERT_CALLBACK: +#if GNUTLS_VERSION_MAJOR < 3 +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "MHD_OPTION_HTTPS_CERT_CALLBACK requires building MHD with GnuTLS >= 3.0\n"); +#endif + return MHD_NO; +#else + if (0 != (daemon->options & MHD_USE_SSL)) + daemon->cert_callback = va_arg (ap, gnutls_certificate_retrieve_function2 *); + break; +#endif #endif #ifdef DAUTH_SUPPORT case MHD_OPTION_DIGEST_AUTH_RANDOM: @@ -2974,6 +2997,7 @@ parse_options_va (struct MHD_Daemon *daemon, case MHD_OPTION_HTTPS_MEM_TRUST: case MHD_OPTION_HTTPS_PRIORITIES: case MHD_OPTION_ARRAY: + case MHD_OPTION_HTTPS_CERT_CALLBACK: if (MHD_YES != parse_options (daemon, servaddr, opt, diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h @@ -31,6 +31,9 @@ #include "microhttpd.h" #if HTTPS_SUPPORT #include <gnutls/gnutls.h> +#if GNUTLS_VERSION_MAJOR >= 3 +#include <gnutls/abstract.h> +#endif #endif #if EPOLL_SUPPORT #include <sys/epoll.h> @@ -1161,6 +1164,14 @@ struct MHD_Daemon */ gnutls_dh_params_t dh_params; +#if GNUTLS_VERSION_MAJOR >= 3 + /** + * Function that can be used to obtain the certificate. Needed + * for SNI support. See #MHD_OPTION_HTTPS_CERT_CALLBACK. + */ + gnutls_certificate_retrieve_function2 *cert_callback; +#endif + /** * Pointer to our SSL/TLS key (in ASCII) in memory. */ diff --git a/src/testcurl/https/Makefile.am b/src/testcurl/https/Makefile.am @@ -19,6 +19,7 @@ check_PROGRAMS = \ test_tls_authentication \ test_https_multi_daemon \ test_https_get \ + test_https_sni \ test_https_get_select \ test_https_get_parallel \ test_https_get_parallel_threads \ @@ -32,6 +33,7 @@ TESTS = \ test_tls_options \ test_https_multi_daemon \ test_https_get \ + test_https_sni \ test_https_get_select \ test_https_get_parallel \ test_https_get_parallel_threads \ @@ -113,6 +115,14 @@ test_https_get_LDADD = \ $(top_builddir)/src/microhttpd/libmicrohttpd.la \ @LIBCURL@ -lgnutls @LIBGCRYPT_LIBS@ +test_https_sni_SOURCES = \ + test_https_sni.c \ + tls_test_common.c +test_https_sni_LDADD = \ + $(top_builddir)/src/testcurl/libcurl_version_check.a \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la \ + @LIBCURL@ -lgnutls @LIBGCRYPT_LIBS@ + test_https_get_select_SOURCES = \ test_https_get_select.c \ tls_test_common.c diff --git a/src/testcurl/https/host1.crt b/src/testcurl/https/host1.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICWTCCAcICCQDc4McLp7j56DANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJa +WjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMQ4wDAYDVQQDDAVob3N0MTEZMBcGCSqGSIb3DQEJARYKdGVzdEBo +b3N0MTAgFw0xMzExMTcxNTE2MzdaGA8yMTEzMTAyNDE1MTYzN1owcDELMAkGA1UE +BhMCWloxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp +ZGdpdHMgUHR5IEx0ZDEOMAwGA1UEAwwFaG9zdDExGTAXBgkqhkiG9w0BCQEWCnRl +c3RAaG9zdDEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKxYiRUzfQnekQn3 +6e+hP/mt/JEkiFzX5TV+E19ue2v4tc7lf+SoLEk2dVt5tGQkHjIGeFFNwCLrgXoi +h3KfP4R1IYe7NFbM+lFVwPceF3inJ75dZD80BxaXQANeh0yC/DhaVJUFNaof2S4+ +7xd8zTL6M11gME+XmR8uaDvW7EBtAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAf62m +Nstj9p9u8T5A5fRnJWfoglH/zfm7IHzht0Wi047O3NFZJ0pOPqV97HuErUA5oBGg +qswnyRGyGMcvL08Bki7Q6NkY7K0ON3lq+ofTkIAHlOKMF+Y/otbjuIDHBfo63tmE +uOcr8XDQGu9R0cfh+qLgicJQd/8cFBhxsL0ls6I= +-----END CERTIFICATE----- diff --git a/src/testcurl/https/host1.key b/src/testcurl/https/host1.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQCsWIkVM30J3pEJ9+nvoT/5rfyRJIhc1+U1fhNfbntr+LXO5X/k +qCxJNnVbebRkJB4yBnhRTcAi64F6Iodynz+EdSGHuzRWzPpRVcD3Hhd4pye+XWQ/ +NAcWl0ADXodMgvw4WlSVBTWqH9kuPu8XfM0y+jNdYDBPl5kfLmg71uxAbQIDAQAB +AoGBAJvq9QmjLSnymtCj4pYSEai2iNpebKdiAlEkoC4j67DArupgohWhN398ryt0 +rYgzTMYBKHSVnI969AYkmtlNzM1yNckRQb/G/tWrkl9re28y2nbAExtHbvLoTk2C +a/EEl1Op+JZNzLoSje7IQMVZoArD3d4aUbfux4XzlO2eRNmZAkEA2pV49QgcOTOJ +PrR5cgekonNdeMtkbZm9dhxgDk9IsYkC0iOxjn/IbeCQN3wuTQ5/yLoiiQ/CQ8w5 +JndF/XpICwJBAMnY37BSRb+XKZeJWP0yjqyFJwzHXkh6IsoSF2OOXSixdiMpthLh +IPzvo6Qxsnha4VvwuDxljHzQFPgMT//CTGcCQQDMs9S+LKU50JDEX4Goj43X8RBl +cp0Poz3yYap3XDqowLYalADRgcvzUq3cuHgoA98Z3W9ASrjUg2o2ItcyBhV3AkAK +bCBgwl7Hnc6P/I+Tw2CKl/WEO2cq5uOU+4opodg9maw39JdqMiW56cXRXJ+Sh17L +mIpq0/OFHll21WvsEORRAkAnDDn/vmW25PSxPVY7tKKJCCkmtBeLQpySfpDgBF+O +QvvokKs2COivc50rmOYNvD1WSsAOspdaSoZUgFw5ikti +-----END RSA PRIVATE KEY----- diff --git a/src/testcurl/https/host2.crt b/src/testcurl/https/host2.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICWTCCAcICCQCJ9nhDYTUBKjANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJa +WjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMQ4wDAYDVQQDDAVob3N0MjEZMBcGCSqGSIb3DQEJARYKdGVzdEBo +b3N0MjAgFw0xMzExMTcxNTE2NDNaGA8yMTEzMTAyNDE1MTY0M1owcDELMAkGA1UE +BhMCWloxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdp +ZGdpdHMgUHR5IEx0ZDEOMAwGA1UEAwwFaG9zdDIxGTAXBgkqhkiG9w0BCQEWCnRl +c3RAaG9zdDIwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALVK8QKMvU96iNL2 +66PKm6xXw9NPHDn+o1TLF1CQRxXMrBYUrObk0961+3n3Z3BXOFHKfSV4E55CpVyz +D1Wcadlt3B9z3ke3HOi0lEa1xNJTMQK/QT3Fx/NURmNg5s9HAsqY4ocb9KHaF5Ex +0TgC0L0aRP0cK1x2TgPEHBNcgGl9AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAEXOi +9rSmVrTN5olIdowctr1vWbGwRCjCnAFXDsqakcDASNthr15LB5kr/mrA3olJjbZh +o+JDvWMY6FN8r1QXW0RL9/obbHxtJpwvAmYVMY9jrR8Rpo38p4RfXlN85g3q9PVx +5IGLaOqLf4hSnKArFL/fzXwxX9b5HBCKlXfiuqM= +-----END CERTIFICATE----- diff --git a/src/testcurl/https/host2.key b/src/testcurl/https/host2.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQC1SvECjL1PeojS9uujypusV8PTTxw5/qNUyxdQkEcVzKwWFKzm +5NPetft592dwVzhRyn0leBOeQqVcsw9VnGnZbdwfc95HtxzotJRGtcTSUzECv0E9 +xcfzVEZjYObPRwLKmOKHG/Sh2heRMdE4AtC9GkT9HCtcdk4DxBwTXIBpfQIDAQAB +AoGAR5Do6TfDt69IefdNeCAQKg2PWUg+fUpfEacGciAyX5GnUSQiSReF58HxHumi +ZL+ZlPgZRQRMwknO23Q4FnSjd66A3E9iHLqkWxRFJWME6E7zgtBrIjctnNu9uYM9 +cw4R6qmXOL7C5sK00KXF2ep8+s+JjrZz61o85QnGGRYA94ECQQDbG6f1B8NKY9T1 +1GDR/++rJbdTVQlZQcKSXMumpU6V3mEV0O9GkYaZzoYvWa3kx6c0np4karrm3QWa +u5E0q1YdAkEA09FPcmzVvIR0+sMWca8QJ/tJUxD6qYo8vLOpO4wt4iTPhGBEU+Q5 +cgXmde3/plVsp0vYxK/NG5XZkoC1fbuC4QJATRGxRlLwsl3jLoUBeVxY5Q5jKYCj +xS2ITwss5vUGa1jJNW9EesH9YmRudoFI1UwU2EFixtRz4Xik3ARV0vzhUQJAfabT +50ASxqMYtczW2peMEPurMqCG4d4ES7iUMqPkcBuAErn8rntbbH19igWmOyi/rLp8 +m6jiFnQdPiAmCbEbYQJAFAKiQl2ZOe3gkSh8MaQilD8Ppog6rod4SQiSmRNsDWPi +IxqXneaGDWhzynC9xr4SwuJ9D5VxW1phNyiveDuYXw== +-----END RSA PRIVATE KEY----- diff --git a/src/testcurl/https/test_https_sni.c b/src/testcurl/https/test_https_sni.c @@ -0,0 +1,289 @@ +/* + This file is part of libmicrohttpd + (C) 2013 Christian Grothoff + + libmicrohttpd is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + libmicrohttpd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libmicrohttpd; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file test_https_sni.c + * @brief Testcase for libmicrohttpd HTTPS with SNI operations + * @author Christian Grothoff + */ +#include "platform.h" +#include "microhttpd.h" +#include <limits.h> +#include <sys/stat.h> +#include <curl/curl.h> +#include <gcrypt.h> +#include "tls_test_common.h" +#include <gnutls/gnutls.h> + +/* This test only works with GnuTLS >= 3.0 */ +#if GNUTLS_VERSION_MAJOR >= 3 + +#include <gnutls/abstract.h> + +/** + * A hostname, server key and certificate. + */ +struct Hosts +{ + struct Hosts *next; + const char *hostname; + gnutls_pcert_st pcrt; + gnutls_privkey_t key; +}; + + +/** + * Linked list of supported TLDs and respective certificates. + */ +static struct Hosts *hosts; + +/* Load the certificate and the private key. + * (This code is largely taken from GnuTLS). + */ +static void +load_keys(const char *hostname, + const char *CERT_FILE, + const char *KEY_FILE) +{ + int ret; + gnutls_datum_t data; + struct Hosts *host; + + host = malloc (sizeof (struct Hosts)); + host->hostname = hostname; + host->next = hosts; + hosts = host; + + ret = gnutls_load_file (CERT_FILE, &data); + if (ret < 0) + { + fprintf (stderr, + "*** Error loading certificate file %s.\n", + CERT_FILE); + exit (1); + } + ret = + gnutls_pcert_import_x509_raw (&host->pcrt, &data, GNUTLS_X509_FMT_PEM, + 0); + if (ret < 0) + { + fprintf (stderr, + "*** Error loading certificate file: %s\n", + gnutls_strerror (ret)); + exit (1); + } + gnutls_free (data.data); + + ret = gnutls_load_file (KEY_FILE, &data); + if (ret < 0) + { + fprintf (stderr, + "*** Error loading key file %s.\n", + KEY_FILE); + exit (1); + } + + gnutls_privkey_init (&host->key); + ret = + gnutls_privkey_import_x509_raw (host->key, + &data, GNUTLS_X509_FMT_PEM, + NULL, 0); + if (ret < 0) + { + fprintf (stderr, + "*** Error loading key file: %s\n", + gnutls_strerror (ret)); + exit (1); + } + gnutls_free (data.data); +} + + + +/** + * @param session the session we are giving a cert for + * @param req_ca_dn NULL on server side + * @param nreqs length of req_ca_dn, and thus 0 on server side + * @param pk_algos NULL on server side + * @param pk_algos_length 0 on server side + * @param pcert list of certificates (to be set) + * @param pcert_length length of pcert (to be set) + * @param pkey the private key (to be set) + */ +static int +sni_callback (gnutls_session_t session, + const gnutls_datum_t* req_ca_dn, + int nreqs, + const gnutls_pk_algorithm_t* pk_algos, + int pk_algos_length, + gnutls_pcert_st** pcert, + unsigned int *pcert_length, + gnutls_privkey_t * pkey) +{ + char name[256]; + size_t name_len; + struct Hosts *host; + unsigned int type; + + name_len = sizeof (name); + if (GNUTLS_E_SUCCESS != + gnutls_server_name_get (session, + name, + &name_len, + &type, + 0 /* index */)) + return -1; + for (host = hosts; NULL != host; host = host->next) + if (0 == strncmp (name, host->hostname, name_len)) + break; + if (NULL == host) + { + fprintf (stderr, + "Need certificate for %.*s\n", + (int) name_len, + name); + return -1; + } +#if 0 + fprintf (stderr, + "Returning certificate for %.*s\n", + (int) name_len, + name); +#endif + *pkey = host->key; + *pcert_length = 1; + *pcert = &host->pcrt; + return 0; +} + + +/* perform a HTTP GET request via SSL/TLS */ +static int +do_get (const char *url) +{ + CURL *c; + struct CBC cbc; + CURLcode errornum; + size_t len; + struct curl_slist *dns_info; + + len = strlen (test_data); + if (NULL == (cbc.buf = malloc (sizeof (char) * len))) + { + fprintf (stderr, MHD_E_MEM); + return -1; + } + cbc.size = len; + cbc.pos = 0; + + c = curl_easy_init (); +#if DEBUG_HTTPS_TEST + curl_easy_setopt (c, CURLOPT_VERBOSE, 1); +#endif + curl_easy_setopt (c, CURLOPT_URL, url); + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 10L); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 10L); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer); + curl_easy_setopt (c, CURLOPT_FILE, &cbc); + + /* perform peer authentication */ + /* TODO merge into send_curl_req */ + curl_easy_setopt (c, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt (c, CURLOPT_SSL_VERIFYHOST, 2); + dns_info = curl_slist_append (NULL, "host1:4233:127.0.0.1"); + dns_info = curl_slist_append (dns_info, "host2:4233:127.0.0.1"); + curl_easy_setopt (c, CURLOPT_RESOLVE, dns_info); + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); + + /* NOTE: use of CONNECTTIMEOUT without also + setting NOSIGNAL results in really weird + crashes on my system! */ + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); + if (CURLE_OK != (errornum = curl_easy_perform (c))) + { + fprintf (stderr, "curl_easy_perform failed: `%s'\n", + curl_easy_strerror (errornum)); + curl_easy_cleanup (c); + free (cbc.buf); + curl_slist_free_all (dns_info); + return errornum; + } + + curl_easy_cleanup (c); + curl_slist_free_all (dns_info); + if (memcmp (cbc.buf, test_data, len) != 0) + { + fprintf (stderr, "Error: local file & received file differ.\n"); + free (cbc.buf); + return -1; + } + + free (cbc.buf); + return 0; +} + + +int +main (int argc, char *const *argv) +{ + unsigned int error_count = 0; + struct MHD_Daemon *d; + + gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); +#ifdef GCRYCTL_INITIALIZATION_FINISHED + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); +#endif + if (0 != curl_global_init (CURL_GLOBAL_ALL)) + { + fprintf (stderr, "Error: %s\n", strerror (errno)); + return -1; + } + load_keys ("host1", "host1.crt", "host1.key"); + load_keys ("host2", "host2.crt", "host2.key"); + d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_SSL | MHD_USE_DEBUG, + 4233, + NULL, NULL, + &http_ahc, NULL, + MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback, + MHD_OPTION_END); + if (d == NULL) + { + fprintf (stderr, MHD_E_SERVER_INIT); + return -1; + } + error_count += do_get ("https://host1:4233/"); + error_count += do_get ("https://host2:4233/"); + + MHD_stop_daemon (d); + curl_global_cleanup (); + return error_count != 0; +} + + +#else + +int main () +{ + fprintf (stderr, + "SNI not supported by GnuTLS < 3.0\n"); + return 0; +} +#endif