41a6c3
diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c
41a6c3
index 926e05e..bbe1d20 100644
41a6c3
--- a/modules/ssl/mod_ssl.c
41a6c3
+++ b/modules/ssl/mod_ssl.c
41a6c3
@@ -333,6 +333,11 @@ static int ssl_hook_pre_config(apr_pool_t *pconf,
41a6c3
     OpenSSL_add_all_algorithms();
41a6c3
     OPENSSL_load_builtin_modules();
41a6c3
 
41a6c3
+    if (OBJ_txt2nid("id-on-dnsSRV") == NID_undef) {
41a6c3
+        (void)OBJ_create("1.3.6.1.5.5.7.8.7", "id-on-dnsSRV",
41a6c3
+                         "SRVName otherName form");
41a6c3
+    }
41a6c3
+
41a6c3
     /*
41a6c3
      * Let us cleanup the ssl library when the module is unloaded
41a6c3
      */
41a6c3
diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c
41a6c3
index eb11a38..27eaa5a 100644
41a6c3
--- a/modules/ssl/ssl_engine_kernel.c
41a6c3
+++ b/modules/ssl/ssl_engine_kernel.c
41a6c3
@@ -1156,6 +1156,7 @@ int ssl_hook_Fixup(request_rec *r)
41a6c3
     /* standard SSL environment variables */
41a6c3
     if (dc->nOptions & SSL_OPT_STDENVVARS) {
41a6c3
         modssl_var_extract_dns(env, sslconn->ssl, r->pool);
41a6c3
+        modssl_var_extract_san_entries(env, sslconn->ssl, r->pool);
41a6c3
 
41a6c3
         for (i = 0; ssl_hook_Fixup_vars[i]; i++) {
41a6c3
             var = (char *)ssl_hook_Fixup_vars[i];
41a6c3
diff --git a/modules/ssl/ssl_engine_vars.c b/modules/ssl/ssl_engine_vars.c
41a6c3
index c508fff..2b7c9ba 100644
41a6c3
--- a/modules/ssl/ssl_engine_vars.c
41a6c3
+++ b/modules/ssl/ssl_engine_vars.c
41a6c3
@@ -42,6 +42,7 @@
41a6c3
 static char *ssl_var_lookup_ssl(apr_pool_t *p, conn_rec *c, request_rec *r, char *var);
41a6c3
 static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, request_rec *r, X509 *xs, char *var);
41a6c3
 static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname, char *var);
41a6c3
+static char *ssl_var_lookup_ssl_cert_san(apr_pool_t *p, X509 *xs, char *var);
41a6c3
 static char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, ASN1_TIME *tm);
41a6c3
 static char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, ASN1_TIME *tm);
41a6c3
 static char *ssl_var_lookup_ssl_cert_serial(apr_pool_t *p, X509 *xs);
41a6c3
@@ -509,6 +510,10 @@ static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, request_rec *r, X509 *xs,
41a6c3
         result = ssl_var_lookup_ssl_cert_dn(p, xsname, var+5);
41a6c3
         resdup = FALSE;
41a6c3
     }
41a6c3
+    else if (strlen(var) > 4 && strcEQn(var, "SAN_", 4)) {
41a6c3
+        result = ssl_var_lookup_ssl_cert_san(p, xs, var+4);
41a6c3
+        resdup = FALSE;
41a6c3
+    }
41a6c3
     else if (strcEQ(var, "A_SIG")) {
41a6c3
         nid = OBJ_obj2nid((ASN1_OBJECT *)(xs->cert_info->signature->algorithm));
41a6c3
         result = apr_pstrdup(p,
41a6c3
@@ -597,6 +602,49 @@ static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname, char *
41a6c3
     return result;
41a6c3
 }
41a6c3
 
41a6c3
+static char *ssl_var_lookup_ssl_cert_san(apr_pool_t *p, X509 *xs, char *var)
41a6c3
+{
41a6c3
+    int type, numlen;
41a6c3
+    const char *onf = NULL;
41a6c3
+    apr_array_header_t *entries;
41a6c3
+
41a6c3
+    if (strcEQn(var, "Email_", 6)) {
41a6c3
+        type = GEN_EMAIL;
41a6c3
+        var += 6;
41a6c3
+    }
41a6c3
+    else if (strcEQn(var, "DNS_", 4)) {
41a6c3
+        type = GEN_DNS;
41a6c3
+        var += 4;
41a6c3
+    }
41a6c3
+    else if (strcEQn(var, "OTHER_", 6)) {
41a6c3
+        type = GEN_OTHERNAME;
41a6c3
+        var += 6;
41a6c3
+        if (strEQn(var, "msUPN_", 6)) {
41a6c3
+            var += 6;
41a6c3
+            onf = "msUPN";
41a6c3
+        }
41a6c3
+        else if (strEQn(var, "dnsSRV_", 7)) {
41a6c3
+            var += 7;
41a6c3
+            onf = "id-on-dnsSRV";
41a6c3
+        }
41a6c3
+        else
41a6c3
+           return NULL;
41a6c3
+    }
41a6c3
+    else
41a6c3
+        return NULL;
41a6c3
+
41a6c3
+    /* sanity check: number must be between 1 and 4 digits */
41a6c3
+    numlen = strspn(var, "0123456789");
41a6c3
+    if ((numlen < 1) || (numlen > 4) || (numlen != strlen(var)))
41a6c3
+        return NULL;
41a6c3
+
41a6c3
+    if (SSL_X509_getSAN(p, xs, type, onf, atoi(var), &entries))
41a6c3
+        /* return the first entry from this 1-element array */
41a6c3
+        return APR_ARRAY_IDX(entries, 0, char *);
41a6c3
+    else
41a6c3
+        return NULL;
41a6c3
+}
41a6c3
+
41a6c3
 static char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, ASN1_TIME *tm)
41a6c3
 {
41a6c3
     char *result;
41a6c3
@@ -890,6 +938,54 @@ void modssl_var_extract_dns(apr_table_t *t, SSL *ssl, apr_pool_t *p)
41a6c3
     }
41a6c3
 }
41a6c3
 
41a6c3
+static void extract_san_array(apr_table_t *t, const char *pfx,
41a6c3
+                              apr_array_header_t *entries, apr_pool_t *p)
41a6c3
+{
41a6c3
+    int i;
41a6c3
+
41a6c3
+    for (i = 0; i < entries->nelts; i++) {
41a6c3
+        const char *key = apr_psprintf(p, "%s_%d", pfx, i);
41a6c3
+        apr_table_setn(t, key, APR_ARRAY_IDX(entries, i, const char *));
41a6c3
+    }
41a6c3
+}
41a6c3
+
41a6c3
+void modssl_var_extract_san_entries(apr_table_t *t, SSL *ssl, apr_pool_t *p)
41a6c3
+{
41a6c3
+    X509 *xs;
41a6c3
+    apr_array_header_t *entries;
41a6c3
+
41a6c3
+    /* subjectAltName entries of the server certificate */
41a6c3
+    xs = SSL_get_certificate(ssl);
41a6c3
+    if (xs) {
41a6c3
+        if (SSL_X509_getSAN(p, xs, GEN_EMAIL, NULL, -1, &entries)) {
41a6c3
+            extract_san_array(t, "SSL_SERVER_SAN_Email", entries, p);
41a6c3
+        }
41a6c3
+        if (SSL_X509_getSAN(p, xs, GEN_DNS, NULL, -1, &entries)) {
41a6c3
+            extract_san_array(t, "SSL_SERVER_SAN_DNS", entries, p);
41a6c3
+        }
41a6c3
+        if (SSL_X509_getSAN(p, xs, GEN_OTHERNAME, "id-on-dnsSRV", -1,
41a6c3
+                               &entries)) {
41a6c3
+            extract_san_array(t, "SSL_SERVER_SAN_OTHER_dnsSRV", entries, p);
41a6c3
+        }
41a6c3
+        /* no need to free xs (refcount does not increase) */
41a6c3
+    }
41a6c3
+
41a6c3
+    /* subjectAltName entries of the client certificate */
41a6c3
+    xs = SSL_get_peer_certificate(ssl);
41a6c3
+    if (xs) {
41a6c3
+        if (SSL_X509_getSAN(p, xs, GEN_EMAIL, NULL, -1, &entries)) {
41a6c3
+            extract_san_array(t, "SSL_CLIENT_SAN_Email", entries, p);
41a6c3
+        }
41a6c3
+        if (SSL_X509_getSAN(p, xs, GEN_DNS, NULL, -1, &entries)) {
41a6c3
+            extract_san_array(t, "SSL_CLIENT_SAN_DNS", entries, p);
41a6c3
+        }
41a6c3
+        if (SSL_X509_getSAN(p, xs, GEN_OTHERNAME, "msUPN", -1, &entries)) {
41a6c3
+            extract_san_array(t, "SSL_CLIENT_SAN_OTHER_msUPN", entries, p);
41a6c3
+        }
41a6c3
+        X509_free(xs);
41a6c3
+    }
41a6c3
+}
41a6c3
+
41a6c3
 /* For an extension type which OpenSSL does not recognize, attempt to
41a6c3
  * parse the extension type as a primitive string.  This will fail for
41a6c3
  * any structured extension type per the docs.  Returns non-zero on
41a6c3
diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h
41a6c3
index a5ede6e..80e1e8e 100644
41a6c3
--- a/modules/ssl/ssl_private.h
41a6c3
+++ b/modules/ssl/ssl_private.h
41a6c3
@@ -974,6 +974,10 @@ void         ssl_var_log_config_register(apr_pool_t *p);
41a6c3
  * allocating from 'p': */
41a6c3
 void modssl_var_extract_dns(apr_table_t *t, SSL *ssl, apr_pool_t *p);
41a6c3
 
41a6c3
+/* Extract SSL_*_SAN_* variables (subjectAltName entries) into table 't'
41a6c3
+ * from SSL object 'ssl', allocating from 'p'. */
41a6c3
+void modssl_var_extract_san_entries(apr_table_t *t, SSL *ssl, apr_pool_t *p);
41a6c3
+
41a6c3
 #ifndef OPENSSL_NO_OCSP
41a6c3
 /* Perform OCSP validation of the current cert in the given context.
41a6c3
  * Returns non-zero on success or zero on failure.  On failure, the
41a6c3
diff --git a/modules/ssl/ssl_util_ssl.c b/modules/ssl/ssl_util_ssl.c
41a6c3
index 588ceba..09a9877 100644
41a6c3
--- a/modules/ssl/ssl_util_ssl.c
41a6c3
+++ b/modules/ssl/ssl_util_ssl.c
41a6c3
@@ -236,22 +236,32 @@ BOOL SSL_X509_getBC(X509 *cert, int *ca, int *pathlen)
41a6c3
     return TRUE;
41a6c3
 }
41a6c3
 
41a6c3
-/* convert a NAME_ENTRY to UTF8 string */
41a6c3
-char *SSL_X509_NAME_ENTRY_to_string(apr_pool_t *p, X509_NAME_ENTRY *xsne)
41a6c3
+/* convert an ASN.1 string to a UTF-8 string (escaping control characters) */
41a6c3
+char *SSL_ASN1_STRING_to_utf8(apr_pool_t *p, ASN1_STRING *asn1str)
41a6c3
 {
41a6c3
     char *result = NULL;
41a6c3
-    BIO* bio;
41a6c3
+    BIO *bio;
41a6c3
     int len;
41a6c3
 
41a6c3
     if ((bio = BIO_new(BIO_s_mem())) == NULL)
41a6c3
         return NULL;
41a6c3
-    ASN1_STRING_print_ex(bio, X509_NAME_ENTRY_get_data(xsne),
41a6c3
-                         ASN1_STRFLGS_ESC_CTRL|ASN1_STRFLGS_UTF8_CONVERT);
41a6c3
+
41a6c3
+    ASN1_STRING_print_ex(bio, asn1str, ASN1_STRFLGS_ESC_CTRL|
41a6c3
+                                       ASN1_STRFLGS_UTF8_CONVERT);
41a6c3
     len = BIO_pending(bio);
41a6c3
-    result = apr_palloc(p, len+1);
41a6c3
-    len = BIO_read(bio, result, len);
41a6c3
-    result[len] = NUL;
41a6c3
+    if (len > 0) {
41a6c3
+        result = apr_palloc(p, len+1);
41a6c3
+        len = BIO_read(bio, result, len);
41a6c3
+        result[len] = NUL;
41a6c3
+    }
41a6c3
     BIO_free(bio);
41a6c3
+    return result;
41a6c3
+}
41a6c3
+
41a6c3
+/* convert a NAME_ENTRY to UTF8 string */
41a6c3
+char *SSL_X509_NAME_ENTRY_to_string(apr_pool_t *p, X509_NAME_ENTRY *xsne)
41a6c3
+{
41a6c3
+    char *result = SSL_ASN1_STRING_to_utf8(p, X509_NAME_ENTRY_get_data(xsne));
41a6c3
     ap_xlate_proto_from_ascii(result, len);
41a6c3
     return result;
41a6c3
 }
41a6c3
@@ -288,51 +298,123 @@ char *SSL_X509_NAME_to_string(apr_pool_t *p, X509_NAME *dn, int maxlen)
41a6c3
     return result;
41a6c3
 }
41a6c3
 
41a6c3
-/* return an array of (RFC 6125 coined) DNS-IDs and CN-IDs in a certificate */
41a6c3
-BOOL SSL_X509_getIDs(apr_pool_t *p, X509 *x509, apr_array_header_t **ids)
41a6c3
+static void parse_otherName_value(apr_pool_t *p, ASN1_TYPE *value,
41a6c3
+                                  const char *onf, apr_array_header_t **entries)
41a6c3
+{
41a6c3
+    const char *str;
41a6c3
+    int nid = onf ? OBJ_txt2nid(onf) : NID_undef;
41a6c3
+
41a6c3
+    if (!value || (nid == NID_undef) || !*entries)
41a6c3
+       return;
41a6c3
+
41a6c3
+    /* 
41a6c3
+     * Currently supported otherName forms (values for "onf"):
41a6c3
+     * "msUPN" (1.3.6.1.4.1.311.20.2.3): Microsoft User Principal Name
41a6c3
+     * "id-on-dnsSRV" (1.3.6.1.5.5.7.8.7): SRVName, as specified in RFC 4985
41a6c3
+     */
41a6c3
+    if ((nid == NID_ms_upn) && (value->type == V_ASN1_UTF8STRING) &&
41a6c3
+        (str = SSL_ASN1_STRING_to_utf8(p, value->value.utf8string))) {
41a6c3
+        APR_ARRAY_PUSH(*entries, const char *) = str;
41a6c3
+    } else if (strEQ(onf, "id-on-dnsSRV") &&
41a6c3
+               (value->type == V_ASN1_IA5STRING) &&
41a6c3
+               (str = SSL_ASN1_STRING_to_utf8(p, value->value.ia5string))) {
41a6c3
+        APR_ARRAY_PUSH(*entries, const char *) = str;
41a6c3
+    }
41a6c3
+}
41a6c3
+
41a6c3
+/* 
41a6c3
+ * Return an array of subjectAltName entries of type "type". If idx is -1,
41a6c3
+ * return all entries of the given type, otherwise return an array consisting
41a6c3
+ * of the n-th occurrence of that type only. Currently supported types:
41a6c3
+ * GEN_EMAIL (rfc822Name)
41a6c3
+ * GEN_DNS (dNSName)
41a6c3
+ * GEN_OTHERNAME (requires the otherName form ["onf"] argument to be supplied,
41a6c3
+ *                see parse_otherName_value for the currently supported forms)
41a6c3
+ */
41a6c3
+BOOL SSL_X509_getSAN(apr_pool_t *p, X509 *x509, int type, const char *onf,
41a6c3
+                     int idx, apr_array_header_t **entries)
41a6c3
 {
41a6c3
     STACK_OF(GENERAL_NAME) *names;
41a6c3
-    BIO *bio;
41a6c3
-    X509_NAME *subj;
41a6c3
-    char **cpp;
41a6c3
-    int i, n;
41a6c3
+    int nid = onf ? OBJ_txt2nid(onf) : NID_undef;
41a6c3
 
41a6c3
-    if (!x509 || !(*ids = apr_array_make(p, 0, sizeof(char *)))) {
41a6c3
-        *ids = NULL;
41a6c3
+    if (!x509 || (type < GEN_OTHERNAME) ||
41a6c3
+        ((type == GEN_OTHERNAME) && (nid == NID_undef)) ||
41a6c3
+        (type > GEN_RID) || (idx < -1) ||
41a6c3
+        !(*entries = apr_array_make(p, 0, sizeof(char *)))) {
41a6c3
+        *entries = NULL;
41a6c3
         return FALSE;
41a6c3
     }
41a6c3
 
41a6c3
-    /* First, the DNS-IDs (dNSName entries in the subjectAltName extension) */
41a6c3
-    if ((names = X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL)) &&
41a6c3
-        (bio = BIO_new(BIO_s_mem()))) {
41a6c3
+    if ((names = X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL))) {
41a6c3
+        int i, n = 0;
41a6c3
         GENERAL_NAME *name;
41a6c3
+        const char *utf8str;
41a6c3
 
41a6c3
         for (i = 0; i < sk_GENERAL_NAME_num(names); i++) {
41a6c3
             name = sk_GENERAL_NAME_value(names, i);
41a6c3
-            if (name->type == GEN_DNS) {
41a6c3
-                ASN1_STRING_print_ex(bio, name->d.ia5, ASN1_STRFLGS_ESC_CTRL|
41a6c3
-                                     ASN1_STRFLGS_UTF8_CONVERT);
41a6c3
-                n = BIO_pending(bio);
41a6c3
-                if (n > 0) {
41a6c3
-                    cpp = (char **)apr_array_push(*ids);
41a6c3
-                    *cpp = apr_palloc(p, n+1);
41a6c3
-                    n = BIO_read(bio, *cpp, n);
41a6c3
-                    (*cpp)[n] = NUL;
41a6c3
+
41a6c3
+            if (name->type != type)
41a6c3
+                continue;
41a6c3
+
41a6c3
+            switch (type) {
41a6c3
+            case GEN_EMAIL:
41a6c3
+            case GEN_DNS:
41a6c3
+                if (((idx == -1) || (n == idx)) &&
41a6c3
+                    (utf8str = SSL_ASN1_STRING_to_utf8(p, name->d.ia5))) {
41a6c3
+                    APR_ARRAY_PUSH(*entries, const char *) = utf8str;
41a6c3
+                }
41a6c3
+                n++;
41a6c3
+                break;
41a6c3
+            case GEN_OTHERNAME:
41a6c3
+                if (OBJ_obj2nid(name->d.otherName->type_id) == nid) {
41a6c3
+                    if (((idx == -1) || (n == idx))) {
41a6c3
+                        parse_otherName_value(p, name->d.otherName->value,
41a6c3
+                                              onf, entries);
41a6c3
+                    }
41a6c3
+                    n++;
41a6c3
                 }
41a6c3
+                break;
41a6c3
+            default:
41a6c3
+                /*
41a6c3
+                 * Not implemented right now:
41a6c3
+                 * GEN_X400 (x400Address)
41a6c3
+                 * GEN_DIRNAME (directoryName)
41a6c3
+                 * GEN_EDIPARTY (ediPartyName)
41a6c3
+                 * GEN_URI (uniformResourceIdentifier)
41a6c3
+                 * GEN_IPADD (iPAddress)
41a6c3
+                 * GEN_RID (registeredID)
41a6c3
+                 */
41a6c3
+                break;
41a6c3
             }
41a6c3
+
41a6c3
+            if ((idx != -1) && (n > idx))
41a6c3
+               break;
41a6c3
         }
41a6c3
-        BIO_free(bio);
41a6c3
-    }
41a6c3
 
41a6c3
-    if (names)
41a6c3
         sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
41a6c3
+    }
41a6c3
+
41a6c3
+    return apr_is_empty_array(*entries) ? FALSE : TRUE;
41a6c3
+}
41a6c3
+
41a6c3
+/* return an array of (RFC 6125 coined) DNS-IDs and CN-IDs in a certificate */
41a6c3
+BOOL SSL_X509_getIDs(apr_pool_t *p, X509 *x509, apr_array_header_t **ids)
41a6c3
+{
41a6c3
+    X509_NAME *subj;
41a6c3
+    int i = -1;
41a6c3
+
41a6c3
+    /* First, the DNS-IDs (dNSName entries in the subjectAltName extension) */
41a6c3
+    if (!x509 ||
41a6c3
+        (SSL_X509_getSAN(p, x509, GEN_DNS, NULL, -1, ids) == FALSE && !*ids)) {
41a6c3
+        *ids = NULL;
41a6c3
+        return FALSE;
41a6c3
+    }
41a6c3
 
41a6c3
     /* Second, the CN-IDs (commonName attributes in the subject DN) */
41a6c3
     subj = X509_get_subject_name(x509);
41a6c3
-    i = -1;
41a6c3
     while ((i = X509_NAME_get_index_by_NID(subj, NID_commonName, i)) != -1) {
41a6c3
-        cpp = (char **)apr_array_push(*ids);
41a6c3
-        *cpp = SSL_X509_NAME_ENTRY_to_string(p, X509_NAME_get_entry(subj, i));
41a6c3
+        APR_ARRAY_PUSH(*ids, const char *) = 
41a6c3
+            SSL_X509_NAME_ENTRY_to_string(p, X509_NAME_get_entry(subj, i));
41a6c3
     }
41a6c3
 
41a6c3
     return apr_is_empty_array(*ids) ? FALSE : TRUE;
41a6c3
diff --git a/modules/ssl/ssl_util_ssl.h b/modules/ssl/ssl_util_ssl.h
41a6c3
index 4b882db..be07ab7 100644
41a6c3
--- a/modules/ssl/ssl_util_ssl.h
41a6c3
+++ b/modules/ssl/ssl_util_ssl.h
41a6c3
@@ -65,8 +65,10 @@ EVP_PKEY   *SSL_read_PrivateKey(char *, EVP_PKEY **, pem_password_cb *, void *);
41a6c3
 int         SSL_smart_shutdown(SSL *ssl);
41a6c3
 BOOL        SSL_X509_isSGC(X509 *);
41a6c3
 BOOL        SSL_X509_getBC(X509 *, int *, int *);
41a6c3
+char       *SSL_ASN1_STRING_to_utf8(apr_pool_t *, ASN1_STRING *);
41a6c3
 char       *SSL_X509_NAME_ENTRY_to_string(apr_pool_t *p, X509_NAME_ENTRY *xsne);
41a6c3
 char       *SSL_X509_NAME_to_string(apr_pool_t *, X509_NAME *, int);
41a6c3
+BOOL        SSL_X509_getSAN(apr_pool_t *, X509 *, int, const char *, int, apr_array_header_t **);
41a6c3
 BOOL        SSL_X509_getIDs(apr_pool_t *, X509 *, apr_array_header_t **);
41a6c3
 BOOL        SSL_X509_match_name(apr_pool_t *, X509 *, const char *, BOOL, server_rec *);
41a6c3
 BOOL        SSL_X509_INFO_load_file(apr_pool_t *, STACK_OF(X509_INFO) *, const char *);