Bladeren bron

add support for SNI

Christian Grothoff 12 jaren geleden
bovenliggende
commit
9889fd9eee

+ 1 - 1
ChangeLog

@@ -1,7 +1,7 @@
 Sat Dec 21 17:26:08 CET 2013
 Sat Dec 21 17:26:08 CET 2013
 	Fixed an issue with a missing argument in the postexample.
 	Fixed an issue with a missing argument in the postexample.
 	Fixed issue with bogus offset increment involving sendfile
 	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
 Mon Dec  9 21:41:57 CET 2013
 	Fix for per-worker daemon pipes enabled with
 	Fix for per-worker daemon pipes enabled with

+ 209 - 38
doc/chapters/tlsauthentication.inc

@@ -1,6 +1,6 @@
 We left the basic authentication chapter with the unsatisfactory conclusion that
 We left the basic authentication chapter with the unsatisfactory conclusion that
 any traffic, including the credentials, could be intercepted by anyone between
 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.
 unsecured lines will be the goal of this chapter.
 
 
 Since version 0.4, the @emph{MHD} library includes support for encrypting the
 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
 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,
 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
 @verbatim
 > openssl req -days 365 -out server.pem -new -x509 -key server.key
 > 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
 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,
 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.
 authentication scheme.
 
 
 
 
@@ -65,12 +65,12 @@ main ()
 @end verbatim
 @end verbatim
 @noindent
 @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
 @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,
   	   		     PORT, NULL, NULL,
-                             &answer_to_connection, NULL, 
+                             &answer_to_connection, NULL,
                              MHD_OPTION_HTTPS_MEM_KEY, key_pem,
                              MHD_OPTION_HTTPS_MEM_KEY, key_pem,
                              MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
                              MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
                              MHD_OPTION_END);
                              MHD_OPTION_END);
@@ -78,10 +78,10 @@ and then we point the @emph{MHD} daemon to it upon initalization.
   if (NULL == daemon)
   if (NULL == daemon)
     {
     {
       printf ("%s\n", cert_pem);
       printf ("%s\n", cert_pem);
-  
+
       free (key_pem);
       free (key_pem);
       free (cert_pem);
       free (cert_pem);
-  
+
       return 1;
       return 1;
     }
     }
 @end verbatim
 @end verbatim
@@ -96,7 +96,7 @@ The rest consists of little new besides some additional memory cleanups.
   MHD_stop_daemon (daemon);
   MHD_stop_daemon (daemon);
   free (key_pem);
   free (key_pem);
   free (cert_pem);
   free (cert_pem);
-  
+
   return 0;
   return 0;
 }
 }
 @end verbatim
 @end verbatim
@@ -110,18 +110,18 @@ The rather unexciting file loader can be found in the complete example @code{tls
 @itemize @bullet
 @itemize @bullet
 @item
 @item
 While the standard @emph{HTTP} port is 80, it is 443 for @emph{HTTPS}. The common internet browsers assume
 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
 @code{https://localhost:8888} explicitly when you test the example, or the browser will not know how to
 handle the answer properly.
 handle the answer properly.
 
 
 @item
 @item
 The remaining weak point is the question how the server will be trusted initially. Either a @emph{CA} signs the
 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.
 that they should not accept certificates of unknown origin.
 
 
 @item
 @item
 The introduced method of certificates makes it mandatory to set an expiration date---making it less feasible to
 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
 @item
 The cryptographic facilities consume memory space and computing time. For this reason, websites usually consists
 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
 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).
 (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}.
 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:
 willing to trust:
 @verbatim
 @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,
   	   		     PORT, NULL, NULL,
-                             &answer_to_connection, NULL, 
+                             &answer_to_connection, NULL,
                              MHD_OPTION_HTTPS_MEM_KEY, key_pem,
                              MHD_OPTION_HTTPS_MEM_KEY, key_pem,
                              MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
                              MHD_OPTION_HTTPS_MEM_CERT, cert_pem,
 			     MHD_OPTION_HTTPS_MEM_TRUST, root_ca_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;
 gnutls_session_t tls_session;
 union MHD_ConnectionInfo *ci;
 union MHD_ConnectionInfo *ci;
 
 
-ci = MHD_get_connection_info (connection, 
+ci = MHD_get_connection_info (connection,
                               MHD_CONNECTION_INFO_GNUTLS_SESSION);
                               MHD_CONNECTION_INFO_GNUTLS_SESSION);
 tls_session = ci->tls_session;
 tls_session = ci->tls_session;
 @end verbatim
 @end verbatim
@@ -172,31 +172,31 @@ You can then extract the client certificate:
  *
  *
  * @param tls_session the TLS session
  * @param tls_session the TLS session
  * @return NULL if no valid client certificate could be found, a pointer
  * @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
 static gnutls_x509_crt_t
-get_client_certificate (gnutls_session_t tls_session) 
+get_client_certificate (gnutls_session_t tls_session)
 {
 {
   unsigned int listsize;
   unsigned int listsize;
   const gnutls_datum_t * pcert;
   const gnutls_datum_t * pcert;
   gnutls_certificate_status_t client_cert_status;
   gnutls_certificate_status_t client_cert_status;
   gnutls_x509_crt_t client_cert;
   gnutls_x509_crt_t client_cert;
 
 
-  if (tls_session == NULL) 
+  if (tls_session == NULL)
     return NULL;
     return NULL;
   if (gnutls_certificate_verify_peers2(tls_session,
   if (gnutls_certificate_verify_peers2(tls_session,
-				       &client_cert_status)) 
+				       &client_cert_status))
     return NULL;
     return NULL;
-  pcert = gnutls_certificate_get_peers(tls_session, 
+  pcert = gnutls_certificate_get_peers(tls_session,
 				       &listsize);
 				       &listsize);
-  if ( (pcert == NULL) || 
-       (listsize == 0)) 
+  if ( (pcert == NULL) ||
+       (listsize == 0))
     {
     {
       fprintf (stderr,
       fprintf (stderr,
 	       "Failed to retrieve client certificate chain\n");
 	       "Failed to retrieve client certificate chain\n");
       return NULL;
       return NULL;
-    }    
-  if (gnutls_x509_crt_init(&client_cert)) 
+    }
+  if (gnutls_x509_crt_init(&client_cert))
     {
     {
       fprintf (stderr,
       fprintf (stderr,
 	       "Failed to initialize client certificate\n");
 	       "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
   /* Note that by passing values between 0 and listsize here, you
      can get access to the CA's certs */
      can get access to the CA's certs */
-  if (gnutls_x509_crt_import(client_cert, 
+  if (gnutls_x509_crt_import(client_cert,
 			     &pcert[0],
 			     &pcert[0],
-			     GNUTLS_X509_FMT_DER)) 
+			     GNUTLS_X509_FMT_DER))
     {
     {
       fprintf (stderr,
       fprintf (stderr,
 	       "Failed to import client certificate\n");
 	       "Failed to import client certificate\n");
       gnutls_x509_crt_deinit(client_cert);
       gnutls_x509_crt_deinit(client_cert);
       return NULL;
       return NULL;
-    }  
+    }
   return client_cert;
   return client_cert;
 }
 }
 @end verbatim
 @end verbatim
@@ -229,15 +229,15 @@ and alternative names:
  * 			to the dn if found
  * 			to the dn if found
  */
  */
 char *
 char *
-cert_auth_get_dn(gnutls_x509_crt_c client_cert) 
+cert_auth_get_dn(gnutls_x509_crt_c client_cert)
 {
 {
   char* buf;
   char* buf;
-  size_t lbuf;  
+  size_t lbuf;
 
 
   lbuf = 0;
   lbuf = 0;
   gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf);
   gnutls_x509_crt_get_dn(client_cert, NULL, &lbuf);
   buf = malloc(lbuf);
   buf = malloc(lbuf);
-  if (buf == NULL) 
+  if (buf == NULL)
     {
     {
       fprintf (stderr,
       fprintf (stderr,
 	       "Failed to allocate memory for certificate dn\n");
 	       "Failed to allocate memory for certificate dn\n");
@@ -260,8 +260,8 @@ cert_auth_get_dn(gnutls_x509_crt_c client_cert)
  */
  */
 char *
 char *
 MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
 MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
-			   int nametype, 
-			   unsigned int index) 
+			   int nametype,
+			   unsigned int index)
 {
 {
   char* buf;
   char* buf;
   size_t lbuf;
   size_t lbuf;
@@ -271,7 +271,7 @@ MHD_cert_auth_get_alt_name(gnutls_x509_crt_t client_cert,
   int result;
   int result;
 
 
   subseq = 0;
   subseq = 0;
-  for (seq=0;;seq++) 
+  for (seq=0;;seq++)
     {
     {
       lbuf = 0;
       lbuf = 0;
       result = gnutls_x509_crt_get_subject_alt_name2(client_cert, seq, NULL, &lbuf,
       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;
 	return NULL;
       if (nametype != (int) type)
       if (nametype != (int) type)
 	continue;
 	continue;
-      if (subseq == index) 
+      if (subseq == index)
 	break;
 	break;
       subseq++;
       subseq++;
     }
     }
   buf = malloc(lbuf);
   buf = malloc(lbuf);
-  if (buf == NULL) 
+  if (buf == NULL)
     {
     {
       fprintf (stderr,
       fprintf (stderr,
 	       "Failed to allocate memory for certificate alt name\n");
 	       "Failed to allocate memory for certificate alt name\n");
       return NULL;
       return NULL;
     }
     }
-  result = gnutls_x509_crt_get_subject_alt_name2(client_cert, 
+  result = gnutls_x509_crt_get_subject_alt_name2(client_cert,
 						 seq,
 						 seq,
 						 buf,
 						 buf,
-						 &lbuf, 
+						 &lbuf,
 						 NULL, NULL);
 						 NULL, NULL);
   if (result != nametype)
   if (result != nametype)
     {
     {
@@ -315,3 +315,174 @@ certificate:
 gnutls_x509_crt_deinit (client_cert);
 gnutls_x509_crt_deinit (client_cert);
 @end verbatim
 @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.

+ 9 - 9
doc/libmicrohttpd-tutorial.texi

@@ -1,10 +1,10 @@
 \input texinfo  @c -*-texinfo-*-
 \input texinfo  @c -*-texinfo-*-
 @finalout
 @finalout
 @setfilename libmicrohttpd-tutorial.info
 @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
 @settitle A tutorial for GNU libmicrohttpd
 @c Unify all the indices into concept index.
 @c Unify all the indices into concept index.
 @syncodeindex fn cp
 @syncodeindex fn cp
@@ -20,11 +20,11 @@
 
 
 @copying
 @copying
 This tutorial documents GNU libmicrohttpd version @value{VERSION}, last
 This tutorial documents GNU libmicrohttpd version @value{VERSION}, last
-updated @value{UPDATED}. 
+updated @value{UPDATED}.
 
 
 Copyright (c)  2008  Sebastian Gerhardt.
 Copyright (c)  2008  Sebastian Gerhardt.
 
 
-Copyright (c)  2010, 2011, 2012  Christian Grothoff.
+Copyright (c)  2010, 2011, 2012, 2013  Christian Grothoff.
 @quotation
 @quotation
 Permission is granted to copy, distribute and/or modify this document
 Permission is granted to copy, distribute and/or modify this document
 under the terms of the GNU Free Documentation License, Version 1.3
 under the terms of the GNU Free Documentation License, Version 1.3
@@ -56,7 +56,7 @@ Free Documentation License".
 @node Top
 @node Top
 @top A Tutorial for GNU libmicrohttpd
 @top A Tutorial for GNU libmicrohttpd
 @insertcopying
 @insertcopying
-@end ifnottex     
+@end ifnottex
 
 
 @menu
 @menu
 * Introduction::
 * Introduction::
@@ -97,8 +97,8 @@ Free Documentation License".
 @chapter Processing POST data
 @chapter Processing POST data
 @include chapters/processingpost.inc
 @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
 @include chapters/largerpost.inc
 
 
 @node Session management
 @node Session management

+ 16 - 0
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
 unchanged to gnutls_priority_init.  If this option is not
 specified, ``NORMAL'' is used.
 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
 @item MHD_OPTION_DIGEST_AUTH_RANDOM
 @cindex digest auth
 @cindex digest auth
 @cindex random
 @cindex random

+ 53 - 53
src/examples/demo.c

@@ -21,7 +21,7 @@
  * @file demo.c
  * @file demo.c
  * @brief complex demonstration site: create directory index, offer
  * @brief complex demonstration site: create directory index, offer
  *        upload via form and HTTP POST, download with mime type detection
  *        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).
  *        high-performance settings (large buffers, thread pool).
  *        If you want to benchmark MHD, this code should be used to
  *        If you want to benchmark MHD, this code should be used to
  *        run tests against.  Note that the number of threads may need
  *        run tests against.  Note that the number of threads may need
@@ -222,7 +222,7 @@ struct ResponseDataContext
    * Response data string.
    * Response data string.
    */
    */
   char *buf;
   char *buf;
-  
+
   /**
   /**
    * Number of bytes allocated for 'buf'.
    * Number of bytes allocated for 'buf'.
    */
    */
@@ -253,12 +253,12 @@ list_directory (struct ResponseDataContext *rdc,
   struct dirent *de;
   struct dirent *de;
 
 
   if (NULL == (dir = opendir (dirname)))
   if (NULL == (dir = opendir (dirname)))
-    return MHD_NO;      
+    return MHD_NO;
   while (NULL != (de = readdir (dir)))
   while (NULL != (de = readdir (dir)))
     {
     {
       if ('.' == de->d_name[0])
       if ('.' == de->d_name[0])
 	continue;
 	continue;
-      if (sizeof (fullname) <= 
+      if (sizeof (fullname) <=
 	  snprintf (fullname, sizeof (fullname),
 	  snprintf (fullname, sizeof (fullname),
 		    "%s/%s",
 		    "%s/%s",
 		    dirname, de->d_name))
 		    dirname, de->d_name))
@@ -278,7 +278,7 @@ list_directory (struct ResponseDataContext *rdc,
 	    break; /* out of memory */
 	    break; /* out of memory */
 	  rdc->buf = r;
 	  rdc->buf = r;
 	}
 	}
-      rdc->off += snprintf (&rdc->buf[rdc->off], 
+      rdc->off += snprintf (&rdc->buf[rdc->off],
 			    rdc->buf_len - rdc->off,
 			    rdc->buf_len - rdc->off,
 			    "<li><a href=\"/%s\">%s</a></li>\n",
 			    "<li><a href=\"/%s\">%s</a></li>\n",
 			    fullname,
 			    fullname,
@@ -305,11 +305,11 @@ update_directory ()
   char dir_name[128];
   char dir_name[128];
   struct stat sbuf;
   struct stat sbuf;
 
 
-  rdc.buf_len = initial_allocation; 
+  rdc.buf_len = initial_allocation;
   if (NULL == (rdc.buf = malloc (rdc.buf_len)))
   if (NULL == (rdc.buf = malloc (rdc.buf_len)))
     {
     {
       update_cached_response (NULL);
       update_cached_response (NULL);
-      return; 
+      return;
     }
     }
   rdc.off = snprintf (rdc.buf, rdc.buf_len,
   rdc.off = snprintf (rdc.buf, rdc.buf_len,
 		      "%s",
 		      "%s",
@@ -342,7 +342,7 @@ update_directory ()
 	  rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
 	  rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
 			       "<h3>%s</h3>\n",
 			       "<h3>%s</h3>\n",
 			       category);
 			       category);
-	  	  
+
 	  if (MHD_NO == list_directory (&rdc, dir_name))
 	  if (MHD_NO == list_directory (&rdc, dir_name))
 	    {
 	    {
 	      free (rdc.buf);
 	      free (rdc.buf);
@@ -352,7 +352,7 @@ update_directory ()
 	}
 	}
     }
     }
   /* we ensured always +1k room, filenames are ~256 bytes,
   /* 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. */
      without need for a final reallocation check. */
   rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
   rdc.off += snprintf (&rdc.buf[rdc.off], rdc.buf_len - rdc.off,
 		       "%s",
 		       "%s",
@@ -427,7 +427,7 @@ do_append (char **ret,
 {
 {
   char *buf;
   char *buf;
   size_t old_len;
   size_t old_len;
-  
+
   if (NULL == *ret)
   if (NULL == *ret)
     old_len = 0;
     old_len = 0;
   else
   else
@@ -471,8 +471,8 @@ process_upload_data (void *cls,
 		     const char *filename,
 		     const char *filename,
 		     const char *content_type,
 		     const char *content_type,
 		     const char *transfer_encoding,
 		     const char *transfer_encoding,
-		     const char *data, 
-		     uint64_t off, 
+		     const char *data,
+		     uint64_t off,
 		     size_t size)
 		     size_t size)
 {
 {
   struct UploadContext *uc = cls;
   struct UploadContext *uc = cls;
@@ -484,10 +484,10 @@ process_upload_data (void *cls,
     return do_append (&uc->language, data, size);
     return do_append (&uc->language, data, size);
   if (0 != strcmp (key, "upload"))
   if (0 != strcmp (key, "upload"))
     {
     {
-      fprintf (stderr, 
+      fprintf (stderr,
 	       "Ignoring unexpected form value `%s'\n",
 	       "Ignoring unexpected form value `%s'\n",
 	       key);
 	       key);
-      return MHD_YES; /* ignore */  
+      return MHD_YES; /* ignore */
     }
     }
   if (NULL == filename)
   if (NULL == filename)
     {
     {
@@ -497,7 +497,7 @@ process_upload_data (void *cls,
   if ( (NULL == uc->category) ||
   if ( (NULL == uc->category) ||
        (NULL == uc->language) )
        (NULL == uc->language) )
     {
     {
-      fprintf (stderr, 
+      fprintf (stderr,
 	       "Missing form data for upload `%s'\n",
 	       "Missing form data for upload `%s'\n",
 	       filename);
 	       filename);
       uc->response = request_refused_response;
       uc->response = request_refused_response;
@@ -523,8 +523,8 @@ process_upload_data (void *cls,
       snprintf (fn, sizeof (fn),
       snprintf (fn, sizeof (fn),
 		"%s/%s",
 		"%s/%s",
 		uc->language,
 		uc->language,
-		uc->category);  
-#ifdef WINDOWS    
+		uc->category);
+#ifdef WINDOWS
       (void) mkdir (fn);
       (void) mkdir (fn);
 #else
 #else
       (void) mkdir (fn, S_IRWXU);
       (void) mkdir (fn, S_IRWXU);
@@ -534,12 +534,12 @@ process_upload_data (void *cls,
 		"%s/%s/%s",
 		"%s/%s/%s",
 		uc->language,
 		uc->language,
 		uc->category,
 		uc->category,
-		filename); 
+		filename);
       for (i=strlen (fn)-1;i>=0;i--)
       for (i=strlen (fn)-1;i>=0;i--)
 	if (! isprint ((int) fn[i]))
 	if (! isprint ((int) fn[i]))
 	  fn[i] = '_';
 	  fn[i] = '_';
-      uc->fd = open (fn, 
-		     O_CREAT | O_EXCL 
+      uc->fd = open (fn,
+		     O_CREAT | O_EXCL
 #if O_LARGEFILE
 #if O_LARGEFILE
 		     | O_LARGEFILE
 		     | O_LARGEFILE
 #endif
 #endif
@@ -547,20 +547,20 @@ process_upload_data (void *cls,
 		     S_IRUSR | S_IWUSR);
 		     S_IRUSR | S_IWUSR);
       if (-1 == uc->fd)
       if (-1 == uc->fd)
 	{
 	{
-	  fprintf (stderr, 
+	  fprintf (stderr,
 		   "Error opening file `%s' for upload: %s\n",
 		   "Error opening file `%s' for upload: %s\n",
 		   fn,
 		   fn,
 		   strerror (errno));
 		   strerror (errno));
 	  uc->response = request_refused_response;
 	  uc->response = request_refused_response;
 	  return MHD_NO;
 	  return MHD_NO;
-	}      
+	}
       uc->filename = strdup (fn);
       uc->filename = strdup (fn);
     }
     }
   if ( (0 != size) &&
   if ( (0 != size) &&
-       (size != write (uc->fd, data, size)) )    
+       (size != write (uc->fd, data, size)) )
     {
     {
       /* write failed; likely: disk full */
       /* write failed; likely: disk full */
-      fprintf (stderr, 
+      fprintf (stderr,
 	       "Error writing to file `%s': %s\n",
 	       "Error writing to file `%s': %s\n",
 	       uc->filename,
 	       uc->filename,
 	       strerror (errno));
 	       strerror (errno));
@@ -573,7 +573,7 @@ process_upload_data (void *cls,
 	  free (uc->filename);
 	  free (uc->filename);
 	  uc->filename = NULL;
 	  uc->filename = NULL;
 	}
 	}
-      return MHD_NO; 
+      return MHD_NO;
     }
     }
   return MHD_YES;
   return MHD_YES;
 }
 }
@@ -610,13 +610,13 @@ response_completed_callback (void *cls,
     (void) close (uc->fd);
     (void) close (uc->fd);
     if (NULL != uc->filename)
     if (NULL != uc->filename)
       {
       {
-	fprintf (stderr, 
+	fprintf (stderr,
 		 "Upload of file `%s' failed (incomplete or aborted), removing file.\n",
 		 "Upload of file `%s' failed (incomplete or aborted), removing file.\n",
 		 uc->filename);
 		 uc->filename);
 	(void) unlink (uc->filename);
 	(void) unlink (uc->filename);
       }
       }
   }
   }
-  if (NULL != uc->filename)    
+  if (NULL != uc->filename)
     free (uc->filename);
     free (uc->filename);
   free (uc);
   free (uc);
 }
 }
@@ -624,7 +624,7 @@ response_completed_callback (void *cls,
 
 
 /**
 /**
  * Return the current directory listing.
  * Return the current directory listing.
- * 
+ *
  * @param connection connection to return the directory for
  * @param connection connection to return the directory for
  * @return MHD_YES on success, MHD_NO on error
  * @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);
   (void) pthread_mutex_lock (&mutex);
   if (NULL == cached_directory_response)
   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);
 			      internal_error_response);
   else
   else
-    ret = MHD_queue_response (connection, 
-			      MHD_HTTP_OK, 
+    ret = MHD_queue_response (connection,
+			      MHD_HTTP_OK,
 			      cached_directory_response);
 			      cached_directory_response);
   (void) pthread_mutex_unlock (&mutex);
   (void) pthread_mutex_unlock (&mutex);
   return ret;
   return ret;
@@ -657,7 +657,7 @@ return_directory_response (struct MHD_Connection *connection)
  * @param version HTTP version
  * @param version HTTP version
  * @param upload_data data from upload (PUT/POST)
  * @param upload_data data from upload (PUT/POST)
  * @param upload_data_size number of bytes in "upload_data"
  * @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
  * @return MHD_YES on success, MHD_NO to drop connection
  */
  */
 static int
 static int
@@ -668,11 +668,11 @@ generate_page (void *cls,
 	       const char *version,
 	       const char *version,
 	       const char *upload_data,
 	       const char *upload_data,
 	       size_t *upload_data_size, void **ptr)
 	       size_t *upload_data_size, void **ptr)
-{  
+{
   struct MHD_Response *response;
   struct MHD_Response *response;
   int ret;
   int ret;
   int fd;
   int fd;
-  struct stat buf;  
+  struct stat buf;
 
 
   if (0 != strcmp (url, "/"))
   if (0 != strcmp (url, "/"))
     {
     {
@@ -685,13 +685,13 @@ generate_page (void *cls,
 	return MHD_NO;  /* unexpected method (we're not polite...) */
 	return MHD_NO;  /* unexpected method (we're not polite...) */
       if ( (0 == stat (&url[1], &buf)) &&
       if ( (0 == stat (&url[1], &buf)) &&
 	   (NULL == strstr (&url[1], "..")) &&
 	   (NULL == strstr (&url[1], "..")) &&
-	   ('/' != url[1]))	   
+	   ('/' != url[1]))
 	fd = open (&url[1], O_RDONLY);
 	fd = open (&url[1], O_RDONLY);
       else
       else
 	fd = -1;
 	fd = -1;
       if (-1 == fd)
       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);
 				   file_not_found_response);
       /* read beginning of the file to determine mime type  */
       /* read beginning of the file to determine mime type  */
       got = read (fd, file_data, sizeof (file_data));
       got = read (fd, file_data, sizeof (file_data));
@@ -701,12 +701,12 @@ generate_page (void *cls,
 	mime = NULL;
 	mime = NULL;
       (void) lseek (fd, 0, SEEK_SET);
       (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)))
 							   fd)))
 	{
 	{
 	  /* internal error (i.e. out of memory) */
 	  /* internal error (i.e. out of memory) */
 	  (void) close (fd);
 	  (void) close (fd);
-	  return MHD_NO; 
+	  return MHD_NO;
 	}
 	}
 
 
       /* add mime type if we had one */
       /* add mime type if we had one */
@@ -714,8 +714,8 @@ generate_page (void *cls,
 	(void) MHD_add_response_header (response,
 	(void) MHD_add_response_header (response,
 					MHD_HTTP_HEADER_CONTENT_TYPE,
 					MHD_HTTP_HEADER_CONTENT_TYPE,
 					mime);
 					mime);
-      ret = MHD_queue_response (connection, 
-				MHD_HTTP_OK, 
+      ret = MHD_queue_response (connection,
+				MHD_HTTP_OK,
 				response);
 				response);
       MHD_destroy_response (response);
       MHD_destroy_response (response);
       return ret;
       return ret;
@@ -744,11 +744,11 @@ generate_page (void *cls,
 	    }
 	    }
 	  *ptr = uc;
 	  *ptr = uc;
 	  return MHD_YES;
 	  return MHD_YES;
-	}     
+	}
       if (0 != *upload_data_size)
       if (0 != *upload_data_size)
 	{
 	{
 	  if (NULL == uc->response)
 	  if (NULL == uc->response)
-	    (void) MHD_post_process (uc->pp, 
+	    (void) MHD_post_process (uc->pp,
 				     upload_data,
 				     upload_data,
 				     *upload_data_size);
 				     *upload_data_size);
 	  *upload_data_size = 0;
 	  *upload_data_size = 0;
@@ -764,8 +764,8 @@ generate_page (void *cls,
 	}
 	}
       if (NULL != uc->response)
       if (NULL != uc->response)
 	{
 	{
-	  return MHD_queue_response (connection, 
-				     MHD_HTTP_FORBIDDEN, 
+	  return MHD_queue_response (connection,
+				     MHD_HTTP_FORBIDDEN,
 				     uc->response);
 				     uc->response);
 	}
 	}
       else
       else
@@ -778,8 +778,8 @@ generate_page (void *cls,
     return return_directory_response (connection);
     return return_directory_response (connection);
 
 
   /* unexpected request, refuse */
   /* unexpected request, refuse */
-  return MHD_queue_response (connection, 
-			     MHD_HTTP_FORBIDDEN, 
+  return MHD_queue_response (connection,
+			     MHD_HTTP_FORBIDDEN,
 			     request_refused_response);
 			     request_refused_response);
 }
 }
 
 
@@ -837,7 +837,7 @@ main (int argc, char *const *argv)
 
 
   if ( (argc != 2) ||
   if ( (argc != 2) ||
        (1 != sscanf (argv[1], "%u", &port)) ||
        (1 != sscanf (argv[1], "%u", &port)) ||
-       (UINT16_MAX < port) ) 
+       (UINT16_MAX < port) )
     {
     {
       fprintf (stderr,
       fprintf (stderr,
 	       "%s PORT\n", argv[0]);
 	       "%s PORT\n", argv[0]);
@@ -864,14 +864,14 @@ main (int argc, char *const *argv)
   mark_as_html (internal_error_response);
   mark_as_html (internal_error_response);
   update_directory ();
   update_directory ();
   d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG
   d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG
-#if EPOLL_SUPPORT 
+#if EPOLL_SUPPORT
 			| MHD_USE_EPOLL_LINUX_ONLY
 			| MHD_USE_EPOLL_LINUX_ONLY
 #endif
 #endif
 			,
 			,
                         port,
                         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
 #if PRODUCTION
 			MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) (64),
 			MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) (64),
 #endif
 #endif

+ 18 - 1
src/include/microhttpd.h

@@ -774,7 +774,24 @@ enum MHD_OPTION
    * Increment to use for growing the read buffer (followed by a
    * Increment to use for growing the read buffer (followed by a
    * `size_t`). Must fit within #MHD_OPTION_CONNECTION_MEMORY_LIMIT.
    * `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
+
 };
 };
 
 
 
 

+ 24 - 0
src/microhttpd/daemon.c

@@ -471,6 +471,13 @@ MHD_init_daemon_certificate (struct MHD_Daemon *daemon)
   gnutls_datum_t key;
   gnutls_datum_t key;
   gnutls_datum_t cert;
   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)
   if (NULL != daemon->https_mem_trust)
     {
     {
       cert.data = (unsigned char *) 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,
 						  &cert, &key,
 						  GNUTLS_X509_FMT_PEM);
 						  GNUTLS_X509_FMT_PEM);
     }
     }
+#if GNUTLS_VERSION_MAJOR >= 3
+  if (NULL != daemon->cert_callback)
+    return 0;
+#endif
 #if HAVE_MESSAGES
 #if HAVE_MESSAGES
   MHD_DLOG (daemon, "You need to specify a certificate and key location\n");
   MHD_DLOG (daemon, "You need to specify a certificate and key location\n");
 #endif
 #endif
@@ -2900,6 +2911,18 @@ parse_options_va (struct MHD_Daemon *daemon,
 	      }
 	      }
 	    }
 	    }
           break;
           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
 #endif
 #ifdef DAUTH_SUPPORT
 #ifdef DAUTH_SUPPORT
 	case MHD_OPTION_DIGEST_AUTH_RANDOM:
 	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_MEM_TRUST:
 		case MHD_OPTION_HTTPS_PRIORITIES:
 		case MHD_OPTION_HTTPS_PRIORITIES:
 		case MHD_OPTION_ARRAY:
 		case MHD_OPTION_ARRAY:
+                case MHD_OPTION_HTTPS_CERT_CALLBACK:
 		  if (MHD_YES != parse_options (daemon,
 		  if (MHD_YES != parse_options (daemon,
 						servaddr,
 						servaddr,
 						opt,
 						opt,

+ 11 - 0
src/microhttpd/internal.h

@@ -31,6 +31,9 @@
 #include "microhttpd.h"
 #include "microhttpd.h"
 #if HTTPS_SUPPORT
 #if HTTPS_SUPPORT
 #include <gnutls/gnutls.h>
 #include <gnutls/gnutls.h>
+#if GNUTLS_VERSION_MAJOR >= 3
+#include <gnutls/abstract.h>
+#endif
 #endif
 #endif
 #if EPOLL_SUPPORT
 #if EPOLL_SUPPORT
 #include <sys/epoll.h>
 #include <sys/epoll.h>
@@ -1161,6 +1164,14 @@ struct MHD_Daemon
    */
    */
   gnutls_dh_params_t dh_params;
   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.
    * Pointer to our SSL/TLS key (in ASCII) in memory.
    */
    */

+ 10 - 0
src/testcurl/https/Makefile.am

@@ -19,6 +19,7 @@ check_PROGRAMS = \
   test_tls_authentication \
   test_tls_authentication \
   test_https_multi_daemon \
   test_https_multi_daemon \
   test_https_get \
   test_https_get \
+  test_https_sni \
   test_https_get_select \
   test_https_get_select \
   test_https_get_parallel \
   test_https_get_parallel \
   test_https_get_parallel_threads \
   test_https_get_parallel_threads \
@@ -32,6 +33,7 @@ TESTS = \
   test_tls_options \
   test_tls_options \
   test_https_multi_daemon \
   test_https_multi_daemon \
   test_https_get \
   test_https_get \
+  test_https_sni \
   test_https_get_select \
   test_https_get_select \
   test_https_get_parallel \
   test_https_get_parallel \
   test_https_get_parallel_threads \
   test_https_get_parallel_threads \
@@ -113,6 +115,14 @@ test_https_get_LDADD  = \
   $(top_builddir)/src/microhttpd/libmicrohttpd.la \
   $(top_builddir)/src/microhttpd/libmicrohttpd.la \
   @LIBCURL@ -lgnutls @LIBGCRYPT_LIBS@
   @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_SOURCES = \
   test_https_get_select.c \
   test_https_get_select.c \
   tls_test_common.c
   tls_test_common.c

+ 15 - 0
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-----

+ 15 - 0
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-----

+ 15 - 0
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-----

+ 15 - 0
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-----

+ 289 - 0
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