diff --git a/docs/manual/mod/mod_ssl.xml b/docs/manual/mod/mod_ssl.xml
index f08d83fbf48..96a982c1d3d 100644
--- a/docs/manual/mod/mod_ssl.xml
+++ b/docs/manual/mod/mod_ssl.xml
@@ -1421,7 +1421,7 @@ SSLCARevocationCheck chain no_crl_for_cert_ok
SSLVerifyClient
Type of Client Certificate verification
-SSLVerifyClient level
+SSLVerifyClient level [accepted-errors]
SSLVerifyClient none
server config
virtual host
@@ -1442,19 +1442,60 @@ before the HTTP response is sent.
The following levels are available for level:
- none:
- no client Certificate is required at all
+ no client certificate is required at all. When this level is used,
+ a second argument for accepted-errors is not permitted.
- optional:
- the client may present a valid Certificate
+ the client may present a valid certificate
- require:
- the client has to present a valid Certificate
+ the client has to present a valid certificate
- optional_no_ca:
- the client may present a valid Certificate
+ the client may present a valid certificate
but it need not to be (successfully) verifiable. This option
- cannot be relied upon for client authentication.
+ cannot be relied upon for client authentication. This is now equivalent to
+ SSLVerifyClient optional X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,X509_V_ERR_CERT_UNTRUSTED,X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE,X509_V_ERR_CERT_HAS_EXPIRED.
+
+The optional second argument accepted-errors can be used to specify a
+comma-separated list of verification errors that should be accepted, even if the
+verification level would otherwise reject the certificate.
+Use of accepted-errors weakens client certificate verification and
+should not be used in production deployments.
+The following shorthand names are available for accepted-errors:
+
+- self-signed:
+ Accepts
X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT and
+ X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN.
+- untrusted-cert:
+ Accepts
X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY and
+ X509_V_ERR_CERT_UNTRUSTED.
+- invalid-signature:
+ Accepts
X509_V_ERR_CERT_SIGNATURE_FAILURE and
+ X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE.
+- expired-cert:
+ Accepts
X509_V_ERR_CERT_HAS_EXPIRED and
+ X509_V_ERR_CERT_NOT_YET_VALID.
+- purpose-mismatch:
+ Accepts
X509_V_ERR_INVALID_PURPOSE.
+
+
+Alternatively, any of the following OpenSSL X509 verification error names can be used directly,
+such as X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
+X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
+X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
+X509_V_ERR_CERT_UNTRUSTED,
+X509_V_ERR_CERT_SIGNATURE_FAILURE,
+X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE,
+X509_V_ERR_CERT_HAS_EXPIRED,
+X509_V_ERR_CERT_NOT_YET_VALID, or
+X509_V_ERR_INVALID_PURPOSE.
Example
-SSLVerifyClient require
+ SSLVerifyClient require purpose-mismatch
+
+
+Example with accepted errors
+
+ SSLVerifyClient optional self-signed,untrusted-cert,purpose-mismatch
diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c
index 8f79d0a8bc0..b6f206f1992 100644
--- a/modules/ssl/mod_ssl.c
+++ b/modules/ssl/mod_ssl.c
@@ -149,9 +149,10 @@ static const command_rec ssl_config_cmds[] = {
"('/path/to/file' - PEM encoded)")
SSL_CMD_SRV(CARevocationCheck, RAW_ARGS,
"SSL CA Certificate Revocation List (CRL) checking mode")
- SSL_CMD_ALL(VerifyClient, TAKE1,
+ SSL_CMD_ALL(VerifyClient, TAKE12,
"SSL Client verify type "
- "('none', 'optional', 'require', 'optional_no_ca')")
+ "('none', 'optional', 'require', 'optional_no_ca' "
+ "[accepted-errors])")
SSL_CMD_ALL(VerifyDepth, TAKE1,
"SSL Client verify depth "
"('N' - number of intermediate certificates)")
diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c
index a38dd943e4c..9344349549d 100644
--- a/modules/ssl/ssl_engine_config.c
+++ b/modules/ssl/ssl_engine_config.c
@@ -138,6 +138,8 @@ static void modssl_ctx_init(modssl_ctx_t *mctx, apr_pool_t *p)
mctx->auth.cipher_suite = NULL;
mctx->auth.verify_depth = UNSET;
mctx->auth.verify_mode = SSL_CVERIFY_UNSET;
+ mctx->auth.verify_error_mask = 0;
+ mctx->auth.verify_error_mask_set = FALSE;
mctx->auth.tls13_ciphers = NULL;
mctx->ocsp_mask = UNSET;
@@ -284,6 +286,14 @@ static void modssl_ctx_cfg_merge(apr_pool_t *p,
cfgMergeString(auth.cipher_suite);
cfgMergeInt(auth.verify_depth);
cfgMerge(auth.verify_mode, SSL_CVERIFY_UNSET);
+ if (add->auth.verify_error_mask_set) {
+ mrg->auth.verify_error_mask = add->auth.verify_error_mask;
+ mrg->auth.verify_error_mask_set = TRUE;
+ }
+ else {
+ mrg->auth.verify_error_mask = base->auth.verify_error_mask;
+ mrg->auth.verify_error_mask_set = base->auth.verify_error_mask_set;
+ }
cfgMergeString(auth.tls13_ciphers);
cfgMergeInt(ocsp_mask);
@@ -405,6 +415,8 @@ void *ssl_config_perdir_create(apr_pool_t *p, char *dir)
dc->szCipherSuite = NULL;
dc->nVerifyClient = SSL_CVERIFY_UNSET;
+ dc->nVerifyClientErrorMask = 0;
+ dc->nVerifyClientErrorMaskSet = FALSE;
dc->nVerifyDepth = UNSET;
dc->szUserName = NULL;
@@ -461,6 +473,14 @@ void *ssl_config_perdir_merge(apr_pool_t *p, void *basev, void *addv)
cfgMergeString(szCipherSuite);
cfgMerge(nVerifyClient, SSL_CVERIFY_UNSET);
+ if (add->nVerifyClientErrorMaskSet) {
+ mrg->nVerifyClientErrorMask = add->nVerifyClientErrorMask;
+ mrg->nVerifyClientErrorMaskSet = TRUE;
+ }
+ else {
+ mrg->nVerifyClientErrorMask = base->nVerifyClientErrorMask;
+ mrg->nVerifyClientErrorMaskSet = base->nVerifyClientErrorMaskSet;
+ }
cfgMergeInt(nVerifyDepth);
cfgMergeString(szUserName);
@@ -1298,24 +1318,136 @@ static const char *ssl_cmd_verify_parse(cmd_parms *parms,
return NULL;
}
+#define SSL_VERIFY_CLIENT_OPTIONAL_NO_CA_ERRORS \
+ (ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT \
+ | ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN \
+ | ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY \
+ | ACCEPT_X509_V_ERR_CERT_UNTRUSTED \
+ | ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE \
+ | ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED)
+
+static const char *ssl_cmd_verify_error_mask_add(cmd_parms *parms,
+ const char *token,
+ unsigned int *mask)
+{
+ if (strcEQ(token, "self-signed")) {
+ *mask |= ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
+ | ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN;
+ }
+ else if (strcEQ(token, "untrusted-cert")) {
+ *mask |= ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
+ | ACCEPT_X509_V_ERR_CERT_UNTRUSTED;
+ }
+ else if (strcEQ(token, "invalid-signature")) {
+ *mask |= ACCEPT_X509_V_ERR_CERT_SIGNATURE_FAILURE
+ | ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
+ }
+ else if (strcEQ(token, "expired-cert")) {
+ *mask |= ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED;
+ }
+ else if (strcEQ(token, "purpose-mismatch") || strcEQ(token, "X509_V_ERR_INVALID_PURPOSE")) {
+ *mask |= ACCEPT_X509_V_ERR_INVALID_PURPOSE;
+ }
+ else if (strcEQ(token, "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT")) {
+ *mask |= ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT;
+ }
+ else if (strcEQ(token, "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN")) {
+ *mask |= ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN;
+ }
+ else if (strcEQ(token, "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY")) {
+ *mask |= ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY;
+ }
+ else if (strcEQ(token, "X509_V_ERR_CERT_UNTRUSTED")) {
+ *mask |= ACCEPT_X509_V_ERR_CERT_UNTRUSTED;
+ }
+ else if (strcEQ(token, "X509_V_ERR_CERT_SIGNATURE_FAILURE")) {
+ *mask |= ACCEPT_X509_V_ERR_CERT_SIGNATURE_FAILURE;
+ }
+ else if (strcEQ(token, "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE")) {
+ *mask |= ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
+ }
+ else if (strcEQ(token, "X509_V_ERR_CERT_HAS_EXPIRED")) {
+ *mask |= ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED;
+ }
+ else if (strcEQ(token, "X509_V_ERR_CERT_NOT_YET_VALID")) {
+ *mask |= ACCEPT_X509_V_ERR_CERT_NOT_YET_VALID;
+ }
+ else {
+ return apr_pstrcat(parms->temp_pool, parms->cmd->name,
+ ": Invalid accepted-errors value '", token, "'",
+ NULL);
+ }
+
+ return NULL;
+}
+
+static const char *ssl_cmd_verify_error_mask_parse(cmd_parms *parms,
+ const char *arg,
+ unsigned int *mask)
+{
+ const char *token;
+ char *list;
+ const char *list_cursor;
+ const char *err;
+
+ *mask = 0;
+ list = apr_pstrdup(parms->temp_pool, arg);
+ list_cursor = list;
+
+ while (*list_cursor) {
+ token = ap_getword(parms->temp_pool, &list_cursor, ',');
+ if (!*token) {
+ return apr_pstrcat(parms->temp_pool, parms->cmd->name,
+ ": Invalid accepted-errors list",
+ NULL);
+ }
+ if ((err = ssl_cmd_verify_error_mask_add(parms, token, mask))) {
+ return err;
+ }
+ }
+
+ return NULL;
+}
+
const char *ssl_cmd_SSLVerifyClient(cmd_parms *cmd,
void *dcfg,
- const char *arg)
+ const char *arg1,
+ const char *arg2)
{
SSLDirConfigRec *dc = (SSLDirConfigRec *)dcfg;
SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
ssl_verify_t mode = SSL_CVERIFY_NONE;
+ unsigned int error_mask = 0;
const char *err;
- if ((err = ssl_cmd_verify_parse(cmd, arg, &mode))) {
+ if ((err = ssl_cmd_verify_parse(cmd, arg1, &mode))) {
return err;
}
+ if (arg2 != NULL) {
+ if (mode == SSL_CVERIFY_NONE) {
+ return apr_pstrcat(cmd->temp_pool, cmd->cmd->name,
+ ": accepted-errors is not allowed when level is 'none'",
+ NULL);
+ }
+
+ if ((err = ssl_cmd_verify_error_mask_parse(cmd, arg2, &error_mask))) {
+ return err;
+ }
+ }
+ else if (mode == SSL_CVERIFY_OPTIONAL_NO_CA) {
+ error_mask = SSL_VERIFY_CLIENT_OPTIONAL_NO_CA_ERRORS;
+ }
+
if (cmd->path) {
dc->nVerifyClient = mode;
+ dc->nVerifyClientErrorMask = error_mask;
+ dc->nVerifyClientErrorMaskSet = TRUE;
}
else {
sc->server->auth.verify_mode = mode;
+ sc->server->auth.verify_error_mask = error_mask;
+ sc->server->auth.verify_error_mask_set = TRUE;
}
return NULL;
diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c
index 2156ab40a49..826df6e4eea 100644
--- a/modules/ssl/ssl_engine_io.c
+++ b/modules/ssl/ssl_engine_io.c
@@ -1482,8 +1482,13 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx)
if ((verify_result != X509_V_OK) ||
sslconn->verify_error)
{
- if (ssl_verify_error_is_optional(verify_result) &&
- (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
+ unsigned int verify_error_mask = sc->server->auth.verify_error_mask;
+
+ if (sslconn->dc && sslconn->dc->nVerifyClient != SSL_CVERIFY_UNSET) {
+ verify_error_mask = sslconn->dc->nVerifyClientErrorMask;
+ }
+
+ if (ssl_verify_error_is_accepted(verify_result, verify_error_mask))
{
/* leaving this log message as an error for the moment,
* according to the mod_ssl docs:
@@ -1496,7 +1501,7 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx)
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02009)
"SSL client authentication failed, "
"accepting certificate based on "
- "\"SSLVerifyClient optional_no_ca\" "
+ "\"SSLVerifyClient accepted-errors\" "
"configuration");
ssl_log_ssl_error(SSLLOG_MARK, APLOG_INFO, server);
diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c
index cb88f0112c6..44d6a74c280 100644
--- a/modules/ssl/ssl_engine_kernel.c
+++ b/modules/ssl/ssl_engine_kernel.c
@@ -1630,6 +1630,7 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
int errdepth = X509_STORE_CTX_get_error_depth(ctx);
int depth = UNSET;
int verify = SSL_CVERIFY_UNSET;
+ unsigned int verify_error_mask = 0;
/*
* Log verification information
@@ -1653,8 +1654,19 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
verify = dc->nVerifyClient;
}
}
- if (!dc || (verify == SSL_CVERIFY_UNSET)) {
- verify = mctx->auth.verify_mode;
+ if (conn->outgoing) {
+ if (!dc || (verify == SSL_CVERIFY_UNSET)) {
+ verify = mctx->auth.verify_mode;
+ }
+ }
+ else {
+ if (!dc || (verify == SSL_CVERIFY_UNSET)) {
+ verify = mctx->auth.verify_mode;
+ verify_error_mask = mctx->auth.verify_error_mask;
+ }
+ else {
+ verify_error_mask = dc->nVerifyClientErrorMask;
+ }
}
if (verify == SSL_CVERIFY_NONE) {
@@ -1666,7 +1678,7 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
return TRUE;
}
- if (ssl_verify_error_is_optional(errnum) &&
+ if (conn->outgoing && ssl_verify_error_is_optional(errnum) &&
(verify == SSL_CVERIFY_OPTIONAL_NO_CA))
{
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, conn, APLOGNO(02037)
@@ -1677,6 +1689,16 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
sslconn->verify_info = "GENEROUS";
ok = TRUE;
}
+ else if (!conn->outgoing && ssl_verify_error_is_accepted(errnum, verify_error_mask))
+ {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, conn, APLOGNO(02037)
+ "Certificate Verification: Verifiable Issuer is "
+ "configured as optional, therefore we're accepting "
+ "the certificate");
+
+ sslconn->verify_info = "GENEROUS";
+ ok = TRUE;
+ }
/*
* Expired certificates vs. "expired" CRLs: by default, OpenSSL
@@ -1714,7 +1736,8 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
/* If there was an optional verification error, it's not
* possible to perform OCSP validation since the issuer may be
* missing/untrusted. Fail in that case. */
- if (ssl_verify_error_is_optional(errnum)) {
+ if (conn->outgoing
+ && ssl_verify_error_is_optional(errnum)) {
X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
errnum = X509_V_ERR_APPLICATION_VERIFICATION;
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn, APLOGNO(02038)
@@ -1722,6 +1745,16 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
"if issuer has not been verified "
"(optional_no_ca configured)");
ok = FALSE;
+ }
+ else if (!conn->outgoing
+ && ssl_verify_error_is_accepted(errnum, verify_error_mask)) {
+ X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
+ errnum = X509_V_ERR_APPLICATION_VERIFICATION;
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn, APLOGNO(02038)
+ "cannot perform OCSP validation for cert "
+ "if issuer has not been verified "
+ "(accepted-errors configured)");
+ ok = FALSE;
} else {
ok = modssl_verify_ocsp(ctx, sc, s, conn, conn->pool);
if (!ok) {
diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h
index 442b8b17ae4..63d7cfd6223 100644
--- a/modules/ssl/ssl_private.h
+++ b/modules/ssl/ssl_private.h
@@ -490,6 +490,27 @@ typedef enum {
|| (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE) \
|| (errnum == X509_V_ERR_CERT_HAS_EXPIRED))
+#define ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT (1U<<0)
+#define ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN (1U<<1)
+#define ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (1U<<2)
+#define ACCEPT_X509_V_ERR_CERT_UNTRUSTED (1U<<3)
+#define ACCEPT_X509_V_ERR_CERT_SIGNATURE_FAILURE (1U<<4)
+#define ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE (1U<<5)
+#define ACCEPT_X509_V_ERR_INVALID_PURPOSE (1U<<6)
+#define ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED (1U<<7)
+#define ACCEPT_X509_V_ERR_CERT_NOT_YET_VALID (1U<<8)
+
+#define ssl_verify_error_is_accepted(errnum, accepted_errors) \
+ ((errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && ((accepted_errors) & ACCEPT_X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT)) \
+ || (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN && ((accepted_errors) & ACCEPT_X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN)) \
+ || (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY && ((accepted_errors) & ACCEPT_X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY)) \
+ || (errnum == X509_V_ERR_CERT_UNTRUSTED && ((accepted_errors) & ACCEPT_X509_V_ERR_CERT_UNTRUSTED)) \
+ || (errnum == X509_V_ERR_CERT_SIGNATURE_FAILURE && ((accepted_errors) & ACCEPT_X509_V_ERR_CERT_SIGNATURE_FAILURE)) \
+ || (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE && ((accepted_errors) & ACCEPT_X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE)) \
+ || (errnum == X509_V_ERR_INVALID_PURPOSE && ((accepted_errors) & ACCEPT_X509_V_ERR_INVALID_PURPOSE)) \
+ || (errnum == X509_V_ERR_CERT_HAS_EXPIRED && ((accepted_errors) & ACCEPT_X509_V_ERR_CERT_HAS_EXPIRED)) \
+ || (errnum == X509_V_ERR_CERT_NOT_YET_VALID && ((accepted_errors) & ACCEPT_X509_V_ERR_CERT_NOT_YET_VALID)))
+
/**
* CRL checking mask (mode | flags)
*/
@@ -791,6 +812,8 @@ typedef struct {
/** for client or downstream server authentication */
int verify_depth;
ssl_verify_t verify_mode;
+ unsigned int verify_error_mask;
+ BOOL verify_error_mask_set;
/** TLSv1.3 has its separate cipher list, separate from the
settings for older TLS protocol versions. Since which one takes
@@ -926,6 +949,8 @@ struct SSLDirConfigRec {
ssl_opt_t nOptionsDel;
const char *szCipherSuite;
ssl_verify_t nVerifyClient;
+ unsigned int nVerifyClientErrorMask;
+ BOOL nVerifyClientErrorMaskSet;
int nVerifyDepth;
const char *szUserName;
apr_size_t nRenegBufferSize;
@@ -976,7 +1001,7 @@ const char *ssl_cmd_SSLHonorCipherOrder(cmd_parms *cmd, void *dcfg, int flag);
const char *ssl_cmd_SSLClientHelloVars(cmd_parms *, void *, int flag);
const char *ssl_cmd_SSLCompression(cmd_parms *, void *, int flag);
const char *ssl_cmd_SSLSessionTickets(cmd_parms *, void *, int flag);
-const char *ssl_cmd_SSLVerifyClient(cmd_parms *, void *, const char *);
+const char *ssl_cmd_SSLVerifyClient(cmd_parms *, void *, const char *, const char *);
const char *ssl_cmd_SSLVerifyDepth(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLSessionCache(cmd_parms *, void *, const char *);
const char *ssl_cmd_SSLSessionCacheTimeout(cmd_parms *, void *, const char *);