Joe, many thanks for your review and valuable comments, first off! As
the person who contributed to the original SNI patch (authored by Peter
Sylvester/EdelWeb), I'll try to address your questions to the best of my
knowledge/ability.

An updated version of the patch (based on Guenter's latest version), is
attached to this message, in two versions: first against r606189 (the
last w/o any SNI related code) and second, against the current trunk
version. I'm grateful for comments, hints about flaws in my reasoning /
incorrect assumptions / omissions, or even objections, if there are.

> My main comment: it's not obvious to me how this actually does what it 
> needs to.  The issue with getting true SNI support is that the hostname 
> sent in the ClientHello must cause a change to the selection of 
> r->server early enough to ensure that any security policy specified in 
> the vhost configuration is applied correctly (e.g. SSLVerifyClient) - 
> not merely selection of the correct SSL_CTX (and hence server cert).

That's correct. However, choosing the appropriate r->server is mainly
handled by ap_read_request() in protocol.c, fortunately: specifically,
ap_read_request() calls ap_update_vhost_from_headers() to select the
right virtual host.

> I can't see how this patch achieves that.  Any clues?

If the request includes a Host: header (which results in r->hostname
being set), then everything is fine as soon as the headers have been
read (i.e. before ssl_hook_ReadReq is called, and of course before
ssl_hook_Access checks for directory-specific access restrictions). We
"only" need to make sure that the correct SSL context (and hence the
right server cert) is selected while the TLS handshake takes place.

> Has a configuration
> with an SSLVerifyClient specified in the named vhost been tested?

Yes, and one specific configuration actually made me tweak the code in
the servername callback further: when modifying the SSL connection,
OpenSSL's SSL_set_SSL_CTX() will only adjust the server cert from the
context, but not additional settings like verify_mode and the verify
callback. These are relevant when SSLVerifyClient is configured at the
*per-server* context (i.e. at the vhost level), and the previous version
of the patch failed to enforce such a configuration, at least in cases
where SSLVerifyClient for the first (=default) VirtualHost was different
than for any subsequent VirtualHosts.

Per-directory SSLVerifyClient settings don't suffer from this problem
(as the correct vhost is selected before ssl_hook_Access is called),
these are enforced by triggering a renegotiation, if needed.

When speaking of renegotiating (but not related to SSLVerifyClient
specifically), note that I removed this code from ssl_hook_Access:

> -     * We will force a renegotiation if we switch to another virtualhost.
> -     */
> -    if (r->hostname && !SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) {
> -        if (ssl_set_vhost_ctx(ssl, r->hostname) && ctx != 
> SSL_get_SSL_CTX(ssl))
> -            renegotiate = TRUE;
> -    }

This applied to connections from clients which did not send an SNI
extension; renegotiating here doesn't make much sense to me since these
clients already received a (possibly) wrong certificate, so the mismatch
has already happened.

On the other hand, I've added some code to ssl_hook_ReadReq which deals
with the case where the client supplies an SNI extension but doesn't
send a Host: header (which is probably rare, but anyway):

> +    if (!r->hostname &&
> +        (servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) {
> +        /* Use the SNI extension as the hostname if no Host: header was sent 
> */
> +        r->hostname = apr_pstrdup(r->pool, servername);
> +        ap_update_vhost_from_headers(r);
> +    }

In reply to your further comments (unless addressed by Guenter already):

> 3) Calling an env var "SSL_TLS_..." is redundant; why is an extra env 
> var necessary here anyway?

It adds a couple of useful options when dealing with SSL requests:

* in a CGI application, you can programmatically determine if the client
  supplied an SNI extension (and possibly redirect the user, or point
  him to further information)

* you can log this information (LogFormat and "%{SSL_TLS_SNI}e" as the
  parameter), which allows to gather statistics about the percentage
  of SNI capable clients your site has, e.g.

* you can use this information in mod_rewrite rules, e.g. for
  rewriting requests based on the absence of an SNI extension (such as
  'RewriteCond %{SSL:SSL_TLS_SNI} =""' - note that I had to add support
  for this in ssl_engine_vars.c, this didn't work so far)

I agree that "SSL_TLS_" is somewhat redundant, but on the other hand
"SSL_" seems to be the proper prefix for all mod_ssl related variables,
and using "SSL_SNI" only might lead to the conclusion that SNI is also
relates to pure SSLv2/v3 connections.

> 4) Why is it desirable to send a fatal TLS alert if no vhost is found 
> matching the hostname in the clienthello?

I've changed that to SSL_TLSEXT_ERR_ALERT_WARNING, which also makes it
less of a dead end for NSS based clients e.g. (such as Firefox).

> This warning has no less weight given the presence of SNI support in the 
> version of OpenSSL against which mod_ssl is built.  Many (probably most) 
> deployed clients do not support SNI and hence cannot support name-based 
> vhosting with SSL.  I don't think this should be omitted, merely 
> reworded.

Right, the previous approach was too crude. I'm adapting the wording of
these warnings based on the presence of support for the SNI extension.

Additional changes, compared to the latest version committed to the
trunk by Guenter:

* renamed most of the functions, to make them more consistent
  with the general style for mod_ssl function names

* moved the callback function from ssl_engine_init.c to
  ssl_engine_kernel.c (where all the other OpenSSL callbacks live)

* merged the (intermediate) ssl_set_vhost_ctx function into
  ssl_callback_ServerNameIndication (was only needed by the code in
  ssl_hook_Access, which is now gone)

* some style improvements (last part of ssl_find_vhost, notably)

* in ssl_callback_ServerNameIndication, add some diagnostics at
  APLOG_DEBUG level

Thanks again,
Kaspar

Index: ssl_private.h
===================================================================
--- ssl_private.h       (revision 606189)
+++ ssl_private.h       (working copy)
@@ -35,6 +35,7 @@
 #include "http_connection.h"
 #include "http_request.h"
 #include "http_protocol.h"
+#include "http_vhost.h"
 #include "util_script.h"
 #include "util_filter.h"
 #include "util_ebcdic.h"
@@ -580,6 +581,9 @@ int          ssl_callback_NewSessionCach
 SSL_SESSION *ssl_callback_GetSessionCacheEntry(SSL *, unsigned char *, int, 
int *);
 void         ssl_callback_DelSessionCacheEntry(SSL_CTX *, SSL_SESSION *);
 void         ssl_callback_LogTracingState(MODSSL_INFO_CB_ARG_TYPE, int, int);
+#ifndef OPENSSL_NO_TLSEXT
+int          ssl_callback_ServerNameIndication(SSL *, int *, modssl_ctx_t *);
+#endif
 
 /**  Session Cache Support  */
 void         ssl_scache_init(server_rec *, apr_pool_t *);
Index: ssl_engine_init.c
===================================================================
--- ssl_engine_init.c   (revision 606189)
+++ ssl_engine_init.c   (working copy)
@@ -355,6 +355,33 @@ static void ssl_init_server_check(server
     }
 }
 
+#ifndef OPENSSL_NO_TLSEXT
+static void ssl_init_ctx_tls_extensions(server_rec *s,
+                                        apr_pool_t *p,
+                                        apr_pool_t *ptemp,
+                                        modssl_ctx_t *mctx)
+{
+    /*
+     * Configure TLS extensions support
+     */
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "Configuring TLS extension handling");
+
+    /*
+     * Server name indication (SNI)
+     */
+    if (!SSL_CTX_set_tlsext_servername_callback(mctx->ssl_ctx,
+                          ssl_callback_ServerNameIndication) ||
+        !SSL_CTX_set_tlsext_servername_arg(mctx->ssl_ctx, mctx)) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                     "Unable to initialize TLS servername extension "
+                     "callback (incompatible OpenSSL version?)");
+        ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, s);
+        ssl_die();
+    }
+}
+#endif
+
 static void ssl_init_ctx_protocol(server_rec *s,
                                   apr_pool_t *p,
                                   apr_pool_t *ptemp,
@@ -711,6 +738,9 @@ static void ssl_init_ctx(server_rec *s,
     if (mctx->pks) {
         /* XXX: proxy support? */
         ssl_init_ctx_cert_chain(s, p, ptemp, mctx);
+#ifndef OPENSSL_NO_TLSEXT
+        ssl_init_ctx_tls_extensions(s, p, ptemp, mctx);
+#endif
     }
 }
 
@@ -1062,7 +1092,11 @@ void ssl_init_CheckServers(server_rec *b
         if ((ps = (server_rec *)apr_hash_get(table, key, klen))) {
             ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
                          base_server,
+#ifdef OPENSSL_NO_TLSEXT
                          "Init: SSL server IP/port conflict: "
+#else
+                         "Init: SSL server IP/port congruence: "
+#endif
                          "%s (%s:%d) vs. %s (%s:%d)",
                          ssl_util_vhostid(p, s),
                          (s->defn_name ? s->defn_name : "unknown"),
@@ -1079,8 +1113,14 @@ void ssl_init_CheckServers(server_rec *b
 
     if (conflict) {
         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server,
+#ifdef OPENSSL_NO_TLSEXT
                      "Init: You should not use name-based "
                      "virtual hosts in conjunction with SSL!!");
+#else
+                     "Init: Name-based SSL virtual hosts only "
+                     "work for clients with TLS server name indication "
+                     "support (RFC 4366)");
+#endif
     }
 }
 
Index: ssl_engine_kernel.c
===================================================================
--- ssl_engine_kernel.c (revision 606189)
+++ ssl_engine_kernel.c (working copy)
@@ -31,6 +31,9 @@
 #include "ssl_private.h"
 
 static void ssl_configure_env(request_rec *r, SSLConnRec *sslconn);
+#ifndef OPENSSL_NO_TLSEXT
+static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s);
+#endif
 
 #define SWITCH_STATUS_LINE "HTTP/1.1 101 Switching Protocols"
 #define UPGRADE_HEADER "Upgrade: TLS/1.0, HTTP/1.1"
@@ -92,6 +95,9 @@ int ssl_hook_ReadReq(request_rec *r)
     SSLSrvConfigRec *sc = mySrvConfig(r->server);
     SSLConnRec *sslconn;
     const char *upgrade;
+#ifndef OPENSSL_NO_TLSEXT
+    const char *servername;
+#endif
     SSL *ssl;
     
     /* Perform TLS upgrade here if "SSLEngine optional" is configured,
@@ -153,6 +159,14 @@ int ssl_hook_ReadReq(request_rec *r)
     if (!ssl) {
         return DECLINED;
     }
+#ifndef OPENSSL_NO_TLSEXT
+    if (!r->hostname &&
+        (servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) {
+        /* Use the SNI extension as the hostname if no Host: header was sent */
+        r->hostname = apr_pstrdup(r->pool, servername);
+        ap_update_vhost_from_headers(r);
+    }
+#endif
     SSL_set_app_data2(ssl, r);
 
     /*
@@ -1063,6 +1077,9 @@ int ssl_hook_Fixup(request_rec *r)
     SSLDirConfigRec *dc = myDirConfig(r);
     apr_table_t *env = r->subprocess_env;
     char *var, *val = "";
+#ifndef OPENSSL_NO_TLSEXT
+    const char *servername;
+#endif
     STACK_OF(X509) *peer_certs;
     SSL *ssl;
     int i;
@@ -1089,6 +1106,13 @@ int ssl_hook_Fixup(request_rec *r)
     /* the always present HTTPS (=HTTP over SSL) flag! */
     apr_table_setn(env, "HTTPS", "on");
 
+#ifndef OPENSSL_NO_TLSEXT
+    /* add content of SNI TLS extension (if supplied with ClientHello) */
+    if ((servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) {
+        apr_table_set(env, "SSL_TLS_SNI", servername);
+    }
+#endif
+
     /* standard SSL environment variables */
     if (dc->nOptions & SSL_OPT_STDENVVARS) {
         for (i = 0; ssl_hook_Fixup_vars[i]; i++) {
@@ -1889,3 +1913,108 @@ void ssl_callback_LogTracingState(MODSSL
     }
 }
 
+#ifndef OPENSSL_NO_TLSEXT
+/*
+ * This callback function is executed when OpenSSL encounters an extended
+ * client hello with a server name indication extension ("SNI", cf. RFC 4366).
+ */
+int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx)
+{
+    const char *servername =
+                SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+
+    if (servername) {
+        conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+        if (c) {
+            if (ap_vhost_iterate_given_conn(c, ssl_find_vhost,
+                                            (void *)servername)) {
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+                              "SSL virtual host for servername %s found",
+                              servername);
+                return SSL_TLSEXT_ERR_OK;
+            }
+            else {
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+                              "No matching SSL virtual host for servername "
+                              "%s found (using default/first virtual host)",
+                              servername);
+                return SSL_TLSEXT_ERR_ALERT_WARNING;
+            }
+        }
+    }
+
+    return SSL_TLSEXT_ERR_NOACK;
+}
+
+/*
+ * Find a (name-based) SSL virtual host where either the ServerName
+ * or one of the ServerAliases matches the supplied name (to be used
+ * with ap_vhost_iterate_given_conn())
+ */
+static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s) 
+{
+    SSLSrvConfigRec *sc;
+    SSL *ssl;
+    BOOL found = FALSE;
+    apr_array_header_t *names;
+    int i;
+
+    /* check ServerName */
+    if (!strcasecmp(servername, s->server_hostname)) {
+        found = TRUE;
+    }
+
+    /* 
+     * if not matched yet, check ServerAlias entries
+     * (adapted from vhost.c:matches_aliases())
+     */
+    if (!found) {
+        names = s->names;
+        if (names) {
+            char **name = (char **)names->elts;
+            for (i = 0; i < names->nelts; ++i) {
+                if (!name[i])
+                    continue;
+                if (!strcasecmp(servername, name[i])) {
+                    found = TRUE;
+                    break;
+                }
+            }
+        }
+    }
+
+    /* if still no match, check ServerAlias entries with wildcards */
+    if (!found) {
+        names = s->wild_names;
+        if (names) {
+            char **name = (char **)names->elts;
+            for (i = 0; i < names->nelts; ++i) {
+                if (!name[i])
+                    continue;
+                if (!ap_strcasecmp_match(servername, name[i])) {
+                    found = TRUE;
+                    break;
+                }
+            }
+        }
+    }
+
+    /* set SSL_CTX (if matched) */
+    if (found && (ssl = ((SSLConnRec *)myConnConfig(c))->ssl) &&
+        (sc = mySrvConfig(s))) {
+        SSL_set_SSL_CTX(ssl, sc->server->ssl_ctx);
+        /*
+         * SSL_set_SSL_CTX() only deals with the server cert,
+         * so we need to duplicate a few additional settings
+         * from the ctx by hand
+         */
+        SSL_set_options(ssl, SSL_CTX_get_options(ssl->ctx));
+        SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ssl->ctx),
+                       SSL_CTX_get_verify_callback(ssl->ctx));
+
+        return 1;
+    }
+
+    return 0;
+}
+#endif
Index: ssl_engine_vars.c
===================================================================
--- ssl_engine_vars.c   (revision 607822)
+++ ssl_engine_vars.c   (working copy)
@@ -320,6 +320,12 @@ static char *ssl_var_lookup_ssl(apr_pool
     else if (ssl != NULL && strcEQ(var, "COMPRESS_METHOD")) {
         result = ssl_var_lookup_ssl_compress_meth(ssl);
     }
+#ifndef OPENSSL_NO_TLSEXT
+    else if (ssl != NULL && strcEQ(var, "TLS_SNI")) {
+        result = apr_pstrdup(p, SSL_get_servername(ssl,
+                                                   TLSEXT_NAMETYPE_host_name));
+    }
+#endif
     return result;
 }
 
Index: ssl_toolkit_compat.h
===================================================================
--- ssl_toolkit_compat.h        (revision 606189)
+++ ssl_toolkit_compat.h        (working copy)
@@ -270,6 +270,12 @@ typedef void (*modssl_popfree_fn)(char *
 #define SSL_SESS_CACHE_NO_INTERNAL  SSL_SESS_CACHE_NO_INTERNAL_LOOKUP
 #endif
 
+#ifndef OPENSSL_NO_TLSEXT
+#ifndef SSL_CTRL_SET_TLSEXT_HOSTNAME
+#define OPENSSL_NO_TLSEXT
+#endif
+#endif
+
 #endif /* SSL_TOOLKIT_COMPAT_H */
 
 /** @} */
Index: ssl_private.h
===================================================================
--- ssl_private.h       (revision 607822)
+++ ssl_private.h       (working copy)
@@ -581,6 +581,9 @@ int          ssl_callback_NewSessionCach
 SSL_SESSION *ssl_callback_GetSessionCacheEntry(SSL *, unsigned char *, int, 
int *);
 void         ssl_callback_DelSessionCacheEntry(SSL_CTX *, SSL_SESSION *);
 void         ssl_callback_LogTracingState(MODSSL_INFO_CB_ARG_TYPE, int, int);
+#ifndef OPENSSL_NO_TLSEXT
+int          ssl_callback_ServerNameIndication(SSL *, int *, modssl_ctx_t *);
+#endif
 
 /**  Session Cache Support  */
 void         ssl_scache_init(server_rec *, apr_pool_t *);
@@ -727,11 +730,6 @@ OCSP_RESPONSE *modssl_dispatch_ocsp_requ
                                             conn_rec *c, apr_pool_t *p);
 #endif
 
-#ifndef OPENSSL_NO_TLSEXT
-int ssl_servername_cb(SSL *ssl, int *al, modssl_ctx_t *mctx);
-int ssl_set_vhost_ctx(SSL *ssl, const char *servername); 
-#endif
-
 #endif /* SSL_PRIVATE_H */
 /** @} */
 
Index: ssl_engine_init.c
===================================================================
--- ssl_engine_init.c   (revision 607822)
+++ ssl_engine_init.c   (working copy)
@@ -135,87 +135,6 @@ static int ssl_tmp_keys_init(server_rec 
     return OK;
 }
 
-#ifndef OPENSSL_NO_TLSEXT
-static int set_ssl_vhost(void *servername, conn_rec *c, server_rec *s) 
-{
-    SSLSrvConfigRec *sc;
-    SSL *ssl;
-    BOOL found = FALSE;
-    apr_array_header_t *names;
-    int i;
-
-    /* check ServerName */
-    if (!strcasecmp(servername, s->server_hostname))
-        found = TRUE;
-
-    /* if not matched yet, check ServerAlias entries */
-    if (!found) {
-        names = s->names;
-        if (names) {
-            char **name = (char **)names->elts;
-            for (i = 0; i < names->nelts; ++i) {
-                if (!name[i])
-                    continue;
-                if (!strcasecmp(servername, name[i])) {
-                    found = TRUE;
-                    break;
-                }
-            }
-        }
-    }
-
-    /* if still no match, check ServerAlias entries with wildcards */
-    if (!found) {
-        names = s->wild_names;
-        if (names) {
-            char **name = (char **)names->elts;
-            for (i = 0; i < names->nelts; ++i) {
-                if (!name[i])
-                    continue;
-                if (!ap_strcasecmp_match(servername, name[i])) {
-                    found = TRUE;
-                    break;
-                }
-            }
-        }
-    }
-
-    /* set SSL_CTX (if matched) */
-    if (found) {
-        if ((ssl = ((SSLConnRec *)myConnConfig(c))->ssl) == NULL)
-            return 0;
-        if (!(sc = mySrvConfig(s)))
-            return 0;
-        SSL_set_SSL_CTX(ssl, sc->server->ssl_ctx);
-        return 1;
-    }
-    return 0;
-}
-
-int ssl_set_vhost_ctx(SSL *ssl, const char *servername) 
-{
-    conn_rec *c;
-
-    if (servername == NULL)   /* should not occur. */
-        return 0;
-    SSL_set_SSL_CTX(ssl, NULL);
-    if (!(c = (conn_rec *)SSL_get_app_data(ssl)))
-        return 0;
-    return ap_vhost_iterate_given_conn(c, set_ssl_vhost, (void *)servername);
-}
-
-int ssl_servername_cb(SSL *ssl, int *al, modssl_ctx_t *mctx)
-{
-    const char *servername =
-                SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
-
-    if (servername)
-        return ssl_set_vhost_ctx(ssl, servername) ? 
-                SSL_TLSEXT_ERR_OK : SSL_TLSEXT_ERR_ALERT_FATAL;
-    return SSL_TLSEXT_ERR_NOACK;
-}
-#endif
-
 /*
  *  Per-module initialization
  */
@@ -436,29 +355,32 @@ static void ssl_init_server_check(server
     }
 }
 
-static void ssl_init_server_extensions(server_rec *s,
-                                       apr_pool_t *p,
-                                       apr_pool_t *ptemp,
-                                       modssl_ctx_t *mctx)
+#ifndef OPENSSL_NO_TLSEXT
+static void ssl_init_ctx_tls_extensions(server_rec *s,
+                                        apr_pool_t *p,
+                                        apr_pool_t *ptemp,
+                                        modssl_ctx_t *mctx)
 {
     /*
      * Configure TLS extensions support
      */
-#ifndef OPENSSL_NO_TLSEXT
     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "Configuring TLS extensions facility");
+                 "Configuring TLS extension handling");
 
+    /*
+     * Server name indication (SNI)
+     */
     if (!SSL_CTX_set_tlsext_servername_callback(mctx->ssl_ctx,
-                                                ssl_servername_cb) ||
+                          ssl_callback_ServerNameIndication) ||
         !SSL_CTX_set_tlsext_servername_arg(mctx->ssl_ctx, mctx)) {
         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "Unable to initialize servername callback - "
-                     "bad OpenSSL version.");
+                     "Unable to initialize TLS servername extension "
+                     "callback (incompatible OpenSSL version?)");
         ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, s);
         ssl_die();
     }
-#endif
 }
+#endif
 
 static void ssl_init_ctx_protocol(server_rec *s,
                                   apr_pool_t *p,
@@ -816,7 +738,9 @@ static void ssl_init_ctx(server_rec *s,
     if (mctx->pks) {
         /* XXX: proxy support? */
         ssl_init_ctx_cert_chain(s, p, ptemp, mctx);
-        ssl_init_server_extensions(s, p, ptemp, mctx);
+#ifndef OPENSSL_NO_TLSEXT
+        ssl_init_ctx_tls_extensions(s, p, ptemp, mctx);
+#endif
     }
 }
 
@@ -1110,16 +1034,13 @@ void ssl_init_ConfigureServer(server_rec
 
 void ssl_init_CheckServers(server_rec *base_server, apr_pool_t *p)
 {
+    server_rec *s, *ps;
     SSLSrvConfigRec *sc;
-    server_rec *s;
-#ifdef OPENSSL_NO_TLSEXT
-    server_rec *ps;
     apr_hash_t *table;
     const char *key;
     apr_ssize_t klen;
 
     BOOL conflict = FALSE;
-#endif
 
     /*
      * Give out warnings when a server has HTTPS configured
@@ -1147,7 +1068,6 @@ void ssl_init_CheckServers(server_rec *b
         }
     }
 
-#ifdef OPENSSL_NO_TLSEXT
     /*
      * Give out warnings when more than one SSL-aware virtual server uses the
      * same IP:port. This doesn't work because mod_ssl then will always use
@@ -1172,7 +1092,11 @@ void ssl_init_CheckServers(server_rec *b
         if ((ps = (server_rec *)apr_hash_get(table, key, klen))) {
             ap_log_error(APLOG_MARK, APLOG_WARNING, 0,
                          base_server,
+#ifdef OPENSSL_NO_TLSEXT
                          "Init: SSL server IP/port conflict: "
+#else
+                         "Init: SSL server IP/port congruence: "
+#endif
                          "%s (%s:%d) vs. %s (%s:%d)",
                          ssl_util_vhostid(p, s),
                          (s->defn_name ? s->defn_name : "unknown"),
@@ -1189,10 +1113,15 @@ void ssl_init_CheckServers(server_rec *b
 
     if (conflict) {
         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server,
+#ifdef OPENSSL_NO_TLSEXT
                      "Init: You should not use name-based "
                      "virtual hosts in conjunction with SSL!!");
-    }
+#else
+                     "Init: Name-based SSL virtual hosts only "
+                     "work for clients with TLS server name indication "
+                     "support (RFC 4366)");
 #endif
+    }
 }
 
 #ifdef SSLC_VERSION_NUMBER
Index: ssl_engine_kernel.c
===================================================================
--- ssl_engine_kernel.c (revision 607822)
+++ ssl_engine_kernel.c (working copy)
@@ -31,6 +31,9 @@
 #include "ssl_private.h"
 
 static void ssl_configure_env(request_rec *r, SSLConnRec *sslconn);
+#ifndef OPENSSL_NO_TLSEXT
+static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s);
+#endif
 
 #define SWITCH_STATUS_LINE "HTTP/1.1 101 Switching Protocols"
 #define UPGRADE_HEADER "Upgrade: TLS/1.0, HTTP/1.1"
@@ -92,6 +95,9 @@ int ssl_hook_ReadReq(request_rec *r)
     SSLSrvConfigRec *sc = mySrvConfig(r->server);
     SSLConnRec *sslconn;
     const char *upgrade;
+#ifndef OPENSSL_NO_TLSEXT
+    const char *servername;
+#endif
     SSL *ssl;
     
     /* Perform TLS upgrade here if "SSLEngine optional" is configured,
@@ -153,6 +159,14 @@ int ssl_hook_ReadReq(request_rec *r)
     if (!ssl) {
         return DECLINED;
     }
+#ifndef OPENSSL_NO_TLSEXT
+    if (!r->hostname &&
+        (servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))) {
+        /* Use the SNI extension as the hostname if no Host: header was sent */
+        r->hostname = apr_pstrdup(r->pool, servername);
+        ap_update_vhost_from_headers(r);
+    }
+#endif
     SSL_set_app_data2(ssl, r);
 
     /*
@@ -297,16 +311,6 @@ int ssl_hook_Access(request_rec *r)
      * the currently active one.
      */
 
-#ifndef OPENSSL_NO_TLSEXT
-    /*
-     * We will force a renegotiation if we switch to another virtualhost.
-     */
-    if (r->hostname && !SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) {
-        if (ssl_set_vhost_ctx(ssl, r->hostname) && ctx != SSL_get_SSL_CTX(ssl))
-            renegotiate = TRUE;
-    }
-#endif
-
     /*
      * Override of SSLCipherSuite
      *
@@ -1074,7 +1078,7 @@ int ssl_hook_Fixup(request_rec *r)
     apr_table_t *env = r->subprocess_env;
     char *var, *val = "";
 #ifndef OPENSSL_NO_TLSEXT
-    const char* servername;
+    const char *servername;
 #endif
     STACK_OF(X509) *peer_certs;
     SSL *ssl;
@@ -1909,3 +1913,108 @@ void ssl_callback_LogTracingState(MODSSL
     }
 }
 
+#ifndef OPENSSL_NO_TLSEXT
+/*
+ * This callback function is executed when OpenSSL encounters an extended
+ * client hello with a server name indication extension ("SNI", cf. RFC 4366).
+ */
+int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx)
+{
+    const char *servername =
+                SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+
+    if (servername) {
+        conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
+        if (c) {
+            if (ap_vhost_iterate_given_conn(c, ssl_find_vhost,
+                                            (void *)servername)) {
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+                              "SSL virtual host for servername %s found",
+                              servername);
+                return SSL_TLSEXT_ERR_OK;
+            }
+            else {
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+                              "No matching SSL virtual host for servername "
+                              "%s found (using default/first virtual host)",
+                              servername);
+                return SSL_TLSEXT_ERR_ALERT_WARNING;
+            }
+        }
+    }
+
+    return SSL_TLSEXT_ERR_NOACK;
+}
+
+/*
+ * Find a (name-based) SSL virtual host where either the ServerName
+ * or one of the ServerAliases matches the supplied name (to be used
+ * with ap_vhost_iterate_given_conn())
+ */
+static int ssl_find_vhost(void *servername, conn_rec *c, server_rec *s) 
+{
+    SSLSrvConfigRec *sc;
+    SSL *ssl;
+    BOOL found = FALSE;
+    apr_array_header_t *names;
+    int i;
+
+    /* check ServerName */
+    if (!strcasecmp(servername, s->server_hostname)) {
+        found = TRUE;
+    }
+
+    /* 
+     * if not matched yet, check ServerAlias entries
+     * (adapted from vhost.c:matches_aliases())
+     */
+    if (!found) {
+        names = s->names;
+        if (names) {
+            char **name = (char **)names->elts;
+            for (i = 0; i < names->nelts; ++i) {
+                if (!name[i])
+                    continue;
+                if (!strcasecmp(servername, name[i])) {
+                    found = TRUE;
+                    break;
+                }
+            }
+        }
+    }
+
+    /* if still no match, check ServerAlias entries with wildcards */
+    if (!found) {
+        names = s->wild_names;
+        if (names) {
+            char **name = (char **)names->elts;
+            for (i = 0; i < names->nelts; ++i) {
+                if (!name[i])
+                    continue;
+                if (!ap_strcasecmp_match(servername, name[i])) {
+                    found = TRUE;
+                    break;
+                }
+            }
+        }
+    }
+
+    /* set SSL_CTX (if matched) */
+    if (found && (ssl = ((SSLConnRec *)myConnConfig(c))->ssl) &&
+        (sc = mySrvConfig(s))) {
+        SSL_set_SSL_CTX(ssl, sc->server->ssl_ctx);
+        /*
+         * SSL_set_SSL_CTX() only deals with the server cert,
+         * so we need to duplicate a few additional settings
+         * from the ctx by hand
+         */
+        SSL_set_options(ssl, SSL_CTX_get_options(ssl->ctx));
+        SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ssl->ctx),
+                       SSL_CTX_get_verify_callback(ssl->ctx));
+
+        return 1;
+    }
+
+    return 0;
+}
+#endif
Index: ssl_engine_vars.c
===================================================================
--- ssl_engine_vars.c   (revision 607822)
+++ ssl_engine_vars.c   (working copy)
@@ -320,6 +320,12 @@ static char *ssl_var_lookup_ssl(apr_pool
     else if (ssl != NULL && strcEQ(var, "COMPRESS_METHOD")) {
         result = ssl_var_lookup_ssl_compress_meth(ssl);
     }
+#ifndef OPENSSL_NO_TLSEXT
+    else if (ssl != NULL && strcEQ(var, "TLS_SNI")) {
+        result = apr_pstrdup(p, SSL_get_servername(ssl,
+                                                   TLSEXT_NAMETYPE_host_name));
+    }
+#endif
     return result;
 }
 

Reply via email to