]> granicus.if.org Git - apache/blob - modules/ssl/ssl_engine_vars.c
Extend expression parser registration to support
[apache] / modules / ssl / ssl_engine_vars.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*                      _             _
18  *  _ __ ___   ___   __| |    ___ ___| |  mod_ssl
19  * | '_ ` _ \ / _ \ / _` |   / __/ __| |  Apache Interface to OpenSSL
20  * | | | | | | (_) | (_| |   \__ \__ \ |
21  * |_| |_| |_|\___/ \__,_|___|___/___/_|
22  *                      |_____|
23  *  ssl_engine_vars.c
24  *  Variable Lookup Facility
25  */
26                              /* ``Those of you who think they
27                                   know everything are very annoying
28                                   to those of us who do.''
29                                                   -- Unknown       */
30 #include "ssl_private.h"
31 #include "mod_ssl.h"
32 #include "ap_expr.h"
33
34 #include "apr_time.h"
35
36 /*  _________________________________________________________________
37 **
38 **  Variable Lookup
39 **  _________________________________________________________________
40 */
41
42 static char *ssl_var_lookup_ssl(apr_pool_t *p, conn_rec *c, request_rec *r, char *var);
43 static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, request_rec *r, X509 *xs, char *var);
44 static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname, char *var);
45 static char *ssl_var_lookup_ssl_cert_san(apr_pool_t *p, X509 *xs, char *var);
46 static char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, ASN1_TIME *tm);
47 static char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, ASN1_TIME *tm);
48 static char *ssl_var_lookup_ssl_cert_serial(apr_pool_t *p, X509 *xs);
49 static char *ssl_var_lookup_ssl_cert_chain(apr_pool_t *p, STACK_OF(X509) *sk, char *var);
50 static char *ssl_var_lookup_ssl_cert_rfc4523_cea(apr_pool_t *p, SSL *ssl);
51 static char *ssl_var_lookup_ssl_cert_PEM(apr_pool_t *p, X509 *xs);
52 static char *ssl_var_lookup_ssl_cert_verify(apr_pool_t *p, conn_rec *c);
53 static char *ssl_var_lookup_ssl_cipher(apr_pool_t *p, conn_rec *c, char *var);
54 static void  ssl_var_lookup_ssl_cipher_bits(SSL *ssl, int *usekeysize, int *algkeysize);
55 static char *ssl_var_lookup_ssl_version(apr_pool_t *p, char *var);
56 static char *ssl_var_lookup_ssl_compress_meth(SSL *ssl);
57
58 static int ssl_is_https(conn_rec *c)
59 {
60     SSLConnRec *sslconn = myConnConfig(c);
61     return sslconn && sslconn->ssl;
62 }
63
64 static const char var_interface[] = "mod_ssl/" AP_SERVER_BASEREVISION;
65 static char var_library_interface[] = MODSSL_LIBRARY_TEXT;
66 static char *var_library = NULL;
67
68 static apr_array_header_t *expr_peer_ext_list_fn(ap_expr_eval_ctx_t *ctx,
69                                                  const void *dummy,
70                                                  const char *arg)
71 {
72     return ssl_ext_list(ctx->p, ctx->c, 1, arg);
73 }
74
75 static const char *expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
76 {
77     char *var = (char *)data;
78     SSLConnRec *sslconn = myConnConfig(ctx->c);
79
80     return sslconn ? ssl_var_lookup_ssl(ctx->p, ctx->c, ctx->r, var) : NULL;
81 }
82
83 static const char *expr_func_fn(ap_expr_eval_ctx_t *ctx, const void *data,
84                                 const char *arg)
85 {
86     char *var = (char *)arg;
87
88     return var ? ssl_var_lookup(ctx->p, ctx->s, ctx->c, ctx->r, var) : NULL;
89 }
90
91 static int ssl_expr_lookup(ap_expr_lookup_parms *parms)
92 {
93     switch (parms->type) {
94     case AP_EXPR_FUNC_VAR:
95         /* for now, we just handle everything that starts with SSL_, but
96          * register our hook as APR_HOOK_LAST
97          * XXX: This can be optimized
98          */
99         if (strcEQn(parms->name, "SSL_", 4)) {
100             *parms->func = expr_var_fn;
101             *parms->data = parms->name + 4;
102             return OK;
103         }
104         break;
105     case AP_EXPR_FUNC_STRING:
106         /* Function SSL() is implemented by us.
107          */
108         if (strcEQ(parms->name, "SSL")) {
109             *parms->func = expr_func_fn;
110             *parms->data = NULL;
111             return OK;
112         }
113         break;
114     case AP_EXPR_FUNC_LIST:
115         if (strcEQ(parms->name, "PeerExtList")) {
116             *parms->func = expr_peer_ext_list_fn;
117             *parms->data = "PeerExtList";
118             return OK;
119         }
120         break;
121     }
122     return DECLINED;
123 }
124
125
126 void ssl_var_register(apr_pool_t *p)
127 {
128     char *cp, *cp2;
129
130     APR_REGISTER_OPTIONAL_FN(ssl_is_https);
131     APR_REGISTER_OPTIONAL_FN(ssl_var_lookup);
132     APR_REGISTER_OPTIONAL_FN(ssl_ext_list);
133
134     /* Perform once-per-process library version determination: */
135     var_library = apr_pstrdup(p, MODSSL_LIBRARY_DYNTEXT);
136
137     if ((cp = strchr(var_library, ' ')) != NULL) {
138         *cp = '/';
139         if ((cp2 = strchr(cp, ' ')) != NULL)
140             *cp2 = NUL;
141     }
142
143     if ((cp = strchr(var_library_interface, ' ')) != NULL) {
144         *cp = '/';
145         if ((cp2 = strchr(cp, ' ')) != NULL)
146             *cp2 = NUL;
147     }
148
149     ap_hook_expr_lookup(ssl_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE);
150 }
151
152 /* This function must remain safe to use for a non-SSL connection. */
153 char *ssl_var_lookup(apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, char *var)
154 {
155     SSLModConfigRec *mc = myModConfig(s);
156     const char *result;
157     BOOL resdup;
158     apr_time_exp_t tm;
159
160     result = NULL;
161     resdup = TRUE;
162
163     /*
164      * When no pool is given try to find one
165      */
166     if (p == NULL) {
167         if (r != NULL)
168             p = r->pool;
169         else if (c != NULL)
170             p = c->pool;
171         else
172             p = mc->pPool;
173     }
174
175     /*
176      * Request dependent stuff
177      */
178     if (r != NULL) {
179         switch (var[0]) {
180         case 'H':
181         case 'h':
182             if (strcEQ(var, "HTTP_USER_AGENT"))
183                 result = apr_table_get(r->headers_in, "User-Agent");
184             else if (strcEQ(var, "HTTP_REFERER"))
185                 result = apr_table_get(r->headers_in, "Referer");
186             else if (strcEQ(var, "HTTP_COOKIE"))
187                 result = apr_table_get(r->headers_in, "Cookie");
188             else if (strcEQ(var, "HTTP_FORWARDED"))
189                 result = apr_table_get(r->headers_in, "Forwarded");
190             else if (strcEQ(var, "HTTP_HOST"))
191                 result = apr_table_get(r->headers_in, "Host");
192             else if (strcEQ(var, "HTTP_PROXY_CONNECTION"))
193                 result = apr_table_get(r->headers_in, "Proxy-Connection");
194             else if (strcEQ(var, "HTTP_ACCEPT"))
195                 result = apr_table_get(r->headers_in, "Accept");
196             else if (strlen(var) > 5 && strcEQn(var, "HTTP:", 5))
197                 /* all other headers from which we are still not know about */
198                 result = apr_table_get(r->headers_in, var+5);
199             break;
200
201         case 'R':
202         case 'r':
203             if (strcEQ(var, "REQUEST_METHOD"))
204                 result = r->method;
205             else if (strcEQ(var, "REQUEST_SCHEME"))
206                 result = ap_http_scheme(r);
207             else if (strcEQ(var, "REQUEST_URI"))
208                 result = r->uri;
209             else if (strcEQ(var, "REQUEST_FILENAME"))
210                 result = r->filename;
211             else if (strcEQ(var, "REMOTE_ADDR"))
212                 result = r->useragent_ip;
213             else if (strcEQ(var, "REMOTE_HOST"))
214                 result = ap_get_remote_host(r->connection, r->per_dir_config,
215                                             REMOTE_NAME, NULL);
216             else if (strcEQ(var, "REMOTE_IDENT"))
217                 result = ap_get_remote_logname(r);
218             else if (strcEQ(var, "REMOTE_USER"))
219                 result = r->user;
220             break;
221
222         case 'S':
223         case 's':
224             if (strcEQn(var, "SSL", 3)) break; /* shortcut common case */
225
226             if (strcEQ(var, "SERVER_ADMIN"))
227                 result = r->server->server_admin;
228             else if (strcEQ(var, "SERVER_NAME"))
229                 result = ap_get_server_name_for_url(r);
230             else if (strcEQ(var, "SERVER_PORT"))
231                 result = apr_psprintf(p, "%u", ap_get_server_port(r));
232             else if (strcEQ(var, "SERVER_PROTOCOL"))
233                 result = r->protocol;
234             else if (strcEQ(var, "SCRIPT_FILENAME"))
235                 result = r->filename;
236             break;
237
238         default:
239             if (strcEQ(var, "PATH_INFO"))
240                 result = r->path_info;
241             else if (strcEQ(var, "QUERY_STRING"))
242                 result = r->args;
243             else if (strcEQ(var, "IS_SUBREQ"))
244                 result = (r->main != NULL ? "true" : "false");
245             else if (strcEQ(var, "DOCUMENT_ROOT"))
246                 result = ap_document_root(r);
247             else if (strcEQ(var, "AUTH_TYPE"))
248                 result = r->ap_auth_type;
249             else if (strcEQ(var, "THE_REQUEST"))
250                 result = r->the_request;
251             else if (strlen(var) > 4 && strcEQn(var, "ENV:", 4)) {
252                 result = apr_table_get(r->notes, var+4);
253                 if (result == NULL)
254                     result = apr_table_get(r->subprocess_env, var+4);
255             }
256             break;
257         }
258     }
259
260     /*
261      * Connection stuff
262      */
263     if (result == NULL && c != NULL) {
264         SSLConnRec *sslconn = myConnConfig(c);
265         if (strlen(var) > 4 && strcEQn(var, "SSL_", 4)
266             && sslconn && sslconn->ssl)
267             result = ssl_var_lookup_ssl(p, c, r, var+4);
268         else if (strcEQ(var, "HTTPS")) {
269             if (sslconn && sslconn->ssl)
270                 result = "on";
271             else
272                 result = "off";
273         }
274     }
275
276     /*
277      * Totally independent stuff
278      */
279     if (result == NULL) {
280         if (strlen(var) > 12 && strcEQn(var, "SSL_VERSION_", 12))
281             result = ssl_var_lookup_ssl_version(p, var+12);
282         else if (strcEQ(var, "SERVER_SOFTWARE"))
283             result = ap_get_server_banner();
284         else if (strcEQ(var, "API_VERSION")) {
285             result = apr_itoa(p, MODULE_MAGIC_NUMBER_MAJOR);
286             resdup = FALSE;
287         }
288         else if (strcEQ(var, "TIME_YEAR")) {
289             apr_time_exp_lt(&tm, apr_time_now());
290             result = apr_psprintf(p, "%02d%02d",
291                                  (tm.tm_year / 100) + 19, tm.tm_year % 100);
292             resdup = FALSE;
293         }
294 #define MKTIMESTR(format, tmfield) \
295             apr_time_exp_lt(&tm, apr_time_now()); \
296             result = apr_psprintf(p, format, tm.tmfield); \
297             resdup = FALSE;
298         else if (strcEQ(var, "TIME_MON")) {
299             MKTIMESTR("%02d", tm_mon+1)
300         }
301         else if (strcEQ(var, "TIME_DAY")) {
302             MKTIMESTR("%02d", tm_mday)
303         }
304         else if (strcEQ(var, "TIME_HOUR")) {
305             MKTIMESTR("%02d", tm_hour)
306         }
307         else if (strcEQ(var, "TIME_MIN")) {
308             MKTIMESTR("%02d", tm_min)
309         }
310         else if (strcEQ(var, "TIME_SEC")) {
311             MKTIMESTR("%02d", tm_sec)
312         }
313         else if (strcEQ(var, "TIME_WDAY")) {
314             MKTIMESTR("%d", tm_wday)
315         }
316         else if (strcEQ(var, "TIME")) {
317             apr_time_exp_lt(&tm, apr_time_now());
318             result = apr_psprintf(p,
319                         "%02d%02d%02d%02d%02d%02d%02d", (tm.tm_year / 100) + 19,
320                         (tm.tm_year % 100), tm.tm_mon+1, tm.tm_mday,
321                         tm.tm_hour, tm.tm_min, tm.tm_sec);
322             resdup = FALSE;
323         }
324         /* all other env-variables from the parent Apache process */
325         else if (strlen(var) > 4 && strcEQn(var, "ENV:", 4)) {
326             result = getenv(var+4);
327         }
328     }
329
330     if (result != NULL && resdup)
331         result = apr_pstrdup(p, result);
332     if (result == NULL)
333         result = "";
334     return (char *)result;
335 }
336
337 static char *ssl_var_lookup_ssl(apr_pool_t *p, conn_rec *c, request_rec *r,
338                                 char *var)
339 {
340     SSLConnRec *sslconn = myConnConfig(c);
341     char *result;
342     X509 *xs;
343     STACK_OF(X509) *sk;
344     SSL *ssl;
345
346     result = NULL;
347
348     ssl = sslconn->ssl;
349     if (strlen(var) > 8 && strcEQn(var, "VERSION_", 8)) {
350         result = ssl_var_lookup_ssl_version(p, var+8);
351     }
352     else if (ssl != NULL && strcEQ(var, "PROTOCOL")) {
353         result = (char *)SSL_get_version(ssl);
354     }
355     else if (ssl != NULL && strcEQ(var, "SESSION_ID")) {
356         char buf[MODSSL_SESSION_ID_STRING_LEN];
357         SSL_SESSION *pSession = SSL_get_session(ssl);
358         if (pSession) {
359             unsigned char *id;
360             unsigned int idlen;
361
362 #ifdef OPENSSL_NO_SSL_INTERN
363             id = (unsigned char *)SSL_SESSION_get_id(pSession, &idlen);
364 #else
365             id = pSession->session_id;
366             idlen = pSession->session_id_length;
367 #endif
368
369             result = apr_pstrdup(p, modssl_SSL_SESSION_id2sz(id, idlen,
370                                                              buf, sizeof(buf)));
371         }
372     }
373     else if(ssl != NULL && strcEQ(var, "SESSION_RESUMED")) {
374         if (SSL_session_reused(ssl) == 1)
375             result = "Resumed";
376         else
377             result = "Initial";
378     }
379     else if (ssl != NULL && strlen(var) >= 6 && strcEQn(var, "CIPHER", 6)) {
380         result = ssl_var_lookup_ssl_cipher(p, c, var+6);
381     }
382     else if (ssl != NULL && strlen(var) > 18 && strcEQn(var, "CLIENT_CERT_CHAIN_", 18)) {
383         sk = SSL_get_peer_cert_chain(ssl);
384         result = ssl_var_lookup_ssl_cert_chain(p, sk, var+18);
385     }
386     else if (ssl != NULL && strcEQ(var, "CLIENT_CERT_RFC4523_CEA")) {
387         result = ssl_var_lookup_ssl_cert_rfc4523_cea(p, ssl);
388     }
389     else if (ssl != NULL && strcEQ(var, "CLIENT_VERIFY")) {
390         result = ssl_var_lookup_ssl_cert_verify(p, c);
391     }
392     else if (ssl != NULL && strlen(var) > 7 && strcEQn(var, "CLIENT_", 7)) {
393         if ((xs = SSL_get_peer_certificate(ssl)) != NULL) {
394             result = ssl_var_lookup_ssl_cert(p, r, xs, var+7);
395             X509_free(xs);
396         }
397     }
398     else if (ssl != NULL && strlen(var) > 7 && strcEQn(var, "SERVER_", 7)) {
399         if ((xs = SSL_get_certificate(ssl)) != NULL) {
400             result = ssl_var_lookup_ssl_cert(p, r, xs, var+7);
401             /* SSL_get_certificate is different from SSL_get_peer_certificate.
402              * No need to X509_free(xs).
403              */
404         }
405     }
406     else if (ssl != NULL && strcEQ(var, "COMPRESS_METHOD")) {
407         result = ssl_var_lookup_ssl_compress_meth(ssl);
408     }
409 #ifdef HAVE_TLSEXT
410     else if (ssl != NULL && strcEQ(var, "TLS_SNI")) {
411         result = apr_pstrdup(p, SSL_get_servername(ssl,
412                                                    TLSEXT_NAMETYPE_host_name));
413     }
414 #endif
415     else if (ssl != NULL && strcEQ(var, "SECURE_RENEG")) {
416         int flag = 0;
417 #ifdef SSL_get_secure_renegotiation_support
418         flag = SSL_get_secure_renegotiation_support(ssl);
419 #endif
420         result = apr_pstrdup(p, flag ? "true" : "false");
421     }
422 #ifdef HAVE_SRP
423     else if (ssl != NULL && strcEQ(var, "SRP_USER")) {
424         if ((result = SSL_get_srp_username(ssl)) != NULL) {
425             result = apr_pstrdup(p, result);
426         }
427     }
428     else if (ssl != NULL && strcEQ(var, "SRP_USERINFO")) {
429         if ((result = SSL_get_srp_userinfo(ssl)) != NULL) {
430             result = apr_pstrdup(p, result);
431         }
432     }
433 #endif
434
435     return result;
436 }
437
438 static char *ssl_var_lookup_ssl_cert_dn_oneline(apr_pool_t *p, request_rec *r,
439                                                 X509_NAME *xsname)
440 {
441     char *result = NULL;
442     SSLDirConfigRec *dc;
443     int legacy_format = 0;
444     if (r) {
445         dc = myDirConfig(r);
446         legacy_format = dc->nOptions & SSL_OPT_LEGACYDNFORMAT;
447     }
448     if (legacy_format) {
449         char *cp = X509_NAME_oneline(xsname, NULL, 0);
450         result = apr_pstrdup(p, cp);
451         OPENSSL_free(cp);
452     }
453     else {
454         BIO* bio;
455         int n;
456         unsigned long flags = XN_FLAG_RFC2253 & ~ASN1_STRFLGS_ESC_MSB;
457         if ((bio = BIO_new(BIO_s_mem())) == NULL)
458             return NULL;
459         X509_NAME_print_ex(bio, xsname, 0, flags);
460         n = BIO_pending(bio);
461         if (n > 0) {
462             result = apr_palloc(p, n+1);
463             n = BIO_read(bio, result, n);
464             result[n] = NUL;
465         }
466         BIO_free(bio);
467     }
468     return result;
469 }
470
471 static char *ssl_var_lookup_ssl_cert(apr_pool_t *p, request_rec *r, X509 *xs,
472                                      char *var)
473 {
474     char *result;
475     BOOL resdup;
476     X509_NAME *xsname;
477     int nid;
478
479     result = NULL;
480     resdup = TRUE;
481
482     if (strcEQ(var, "M_VERSION")) {
483         result = apr_psprintf(p, "%lu", X509_get_version(xs)+1);
484         resdup = FALSE;
485     }
486     else if (strcEQ(var, "M_SERIAL")) {
487         result = ssl_var_lookup_ssl_cert_serial(p, xs);
488     }
489     else if (strcEQ(var, "V_START")) {
490         result = ssl_var_lookup_ssl_cert_valid(p, X509_get_notBefore(xs));
491     }
492     else if (strcEQ(var, "V_END")) {
493         result = ssl_var_lookup_ssl_cert_valid(p, X509_get_notAfter(xs));
494     }
495     else if (strcEQ(var, "V_REMAIN")) {
496         result = ssl_var_lookup_ssl_cert_remain(p, X509_get_notAfter(xs));
497         resdup = FALSE;
498     }
499     else if (*var && strcEQ(var+1, "_DN")) {
500         if (*var == 'S')
501             xsname = X509_get_subject_name(xs);
502         else if (*var == 'I')
503             xsname = X509_get_issuer_name(xs);
504         else
505             return NULL;
506         result = ssl_var_lookup_ssl_cert_dn_oneline(p, r, xsname);
507         resdup = FALSE;
508     }
509     else if (strlen(var) > 5 && strcEQn(var+1, "_DN_", 4)) {
510         if (*var == 'S')
511             xsname = X509_get_subject_name(xs);
512         else if (*var == 'I')
513             xsname = X509_get_issuer_name(xs);
514         else
515             return NULL;
516         result = ssl_var_lookup_ssl_cert_dn(p, xsname, var+5);
517         resdup = FALSE;
518     }
519     else if (strlen(var) > 4 && strcEQn(var, "SAN_", 4)) {
520         result = ssl_var_lookup_ssl_cert_san(p, xs, var+4);
521         resdup = FALSE;
522     }
523     else if (strcEQ(var, "A_SIG")) {
524         nid = OBJ_obj2nid((ASN1_OBJECT *)(xs->cert_info->signature->algorithm));
525         result = apr_pstrdup(p,
526                              (nid == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(nid));
527         resdup = FALSE;
528     }
529     else if (strcEQ(var, "A_KEY")) {
530         nid = OBJ_obj2nid((ASN1_OBJECT *)(xs->cert_info->key->algor->algorithm));
531         result = apr_pstrdup(p,
532                              (nid == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(nid));
533         resdup = FALSE;
534     }
535     else if (strcEQ(var, "CERT")) {
536         result = ssl_var_lookup_ssl_cert_PEM(p, xs);
537     }
538
539     if (resdup)
540         result = apr_pstrdup(p, result);
541     return result;
542 }
543
544 /* In this table, .extract is non-zero if RDNs using the NID should be
545  * extracted to for the SSL_{CLIENT,SERVER}_{I,S}_DN_* environment
546  * variables. */
547 static const struct {
548     char *name;
549     int   nid;
550     int   extract;
551 } ssl_var_lookup_ssl_cert_dn_rec[] = {
552     { "C",     NID_countryName,            1 },
553     { "ST",    NID_stateOrProvinceName,    1 }, /* officially    (RFC2156) */
554     { "SP",    NID_stateOrProvinceName,    0 }, /* compatibility (SSLeay)  */
555     { "L",     NID_localityName,           1 },
556     { "O",     NID_organizationName,       1 },
557     { "OU",    NID_organizationalUnitName, 1 },
558     { "CN",    NID_commonName,             1 },
559     { "T",     NID_title,                  1 },
560     { "I",     NID_initials,               1 },
561     { "G",     NID_givenName,              1 },
562     { "S",     NID_surname,                1 },
563     { "D",     NID_description,            1 },
564 #ifdef NID_userId
565     { "UID",   NID_userId,                 1 },
566 #endif
567     { "Email", NID_pkcs9_emailAddress,     1 },
568     { NULL,    0,                          0 }
569 };
570
571 static char *ssl_var_lookup_ssl_cert_dn(apr_pool_t *p, X509_NAME *xsname, char *var)
572 {
573     char *result, *ptr;
574     X509_NAME_ENTRY *xsne;
575     int i, j, n, idx = 0;
576     apr_size_t varlen;
577
578     /* if an _N suffix is used, find the Nth attribute of given name */
579     ptr = strchr(var, '_');
580     if (ptr != NULL && strspn(ptr + 1, "0123456789") == strlen(ptr + 1)) {
581         idx = atoi(ptr + 1);
582         varlen = ptr - var;
583     } else {
584         varlen = strlen(var);
585     }
586
587     result = NULL;
588
589     for (i = 0; ssl_var_lookup_ssl_cert_dn_rec[i].name != NULL; i++) {
590         if (strEQn(var, ssl_var_lookup_ssl_cert_dn_rec[i].name, varlen)
591             && strlen(ssl_var_lookup_ssl_cert_dn_rec[i].name) == varlen) {
592             for (j = 0; j < sk_X509_NAME_ENTRY_num((STACK_OF(X509_NAME_ENTRY) *)
593                                                    xsname->entries);
594                  j++) {
595                 xsne = sk_X509_NAME_ENTRY_value((STACK_OF(X509_NAME_ENTRY) *)
596                                                 xsname->entries, j);
597
598                 n =OBJ_obj2nid((ASN1_OBJECT *)X509_NAME_ENTRY_get_object(xsne));
599
600                 if (n == ssl_var_lookup_ssl_cert_dn_rec[i].nid && idx-- == 0) {
601                     result = modssl_X509_NAME_ENTRY_to_string(p, xsne);
602                     break;
603                 }
604             }
605             break;
606         }
607     }
608     return result;
609 }
610
611 static char *ssl_var_lookup_ssl_cert_san(apr_pool_t *p, X509 *xs, char *var)
612 {
613     int type, numlen;
614     const char *onf = NULL;
615     apr_array_header_t *entries;
616
617     if (strcEQn(var, "Email_", 6)) {
618         type = GEN_EMAIL;
619         var += 6;
620     }
621     else if (strcEQn(var, "DNS_", 4)) {
622         type = GEN_DNS;
623         var += 4;
624     }
625     else if (strcEQn(var, "OTHER_", 6)) {
626         type = GEN_OTHERNAME;
627         var += 6;
628         if (strEQn(var, "msUPN_", 6)) {
629             var += 6;
630             onf = "msUPN";
631         }
632         else if (strEQn(var, "dnsSRV_", 7)) {
633             var += 7;
634             onf = "id-on-dnsSRV";
635         }
636         else
637            return NULL;
638     }
639     else
640         return NULL;
641
642     /* sanity check: number must be between 1 and 4 digits */
643     numlen = strspn(var, "0123456789");
644     if ((numlen < 1) || (numlen > 4) || (numlen != strlen(var)))
645         return NULL;
646
647     if (modssl_X509_getSAN(p, xs, type, onf, atoi(var), &entries))
648         /* return the first entry from this 1-element array */
649         return APR_ARRAY_IDX(entries, 0, char *);
650     else
651         return NULL;
652 }
653
654 static char *ssl_var_lookup_ssl_cert_valid(apr_pool_t *p, ASN1_TIME *tm)
655 {
656     char *result;
657     BIO* bio;
658     int n;
659
660     if ((bio = BIO_new(BIO_s_mem())) == NULL)
661         return NULL;
662     ASN1_TIME_print(bio, tm);
663     n = BIO_pending(bio);
664     result = apr_pcalloc(p, n+1);
665     n = BIO_read(bio, result, n);
666     result[n] = NUL;
667     BIO_free(bio);
668     return result;
669 }
670
671 #define DIGIT2NUM(x) (((x)[0] - '0') * 10 + (x)[1] - '0')
672
673 /* Return a string giving the number of days remaining until 'tm', or
674  * "0" if this can't be determined. */
675 static char *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, ASN1_TIME *tm)
676 {
677     apr_time_t then, now = apr_time_now();
678     apr_time_exp_t exp = {0};
679     long diff;
680     unsigned char *dp;
681
682     /* Fail if the time isn't a valid ASN.1 TIME; RFC3280 mandates
683      * that the seconds digits are present even though ASN.1
684      * doesn't. */
685     if ((tm->type == V_ASN1_UTCTIME && tm->length < 11) ||
686         (tm->type == V_ASN1_GENERALIZEDTIME && tm->length < 13) ||
687         !ASN1_TIME_check(tm)) {
688         return apr_pstrdup(p, "0");
689     }
690
691     if (tm->type == V_ASN1_UTCTIME) {
692         exp.tm_year = DIGIT2NUM(tm->data);
693         if (exp.tm_year <= 50) exp.tm_year += 100;
694         dp = tm->data + 2;
695     } else {
696         exp.tm_year = DIGIT2NUM(tm->data) * 100 + DIGIT2NUM(tm->data + 2) - 1900;
697         dp = tm->data + 4;
698     }
699
700     exp.tm_mon = DIGIT2NUM(dp) - 1;
701     exp.tm_mday = DIGIT2NUM(dp + 2) + 1;
702     exp.tm_hour = DIGIT2NUM(dp + 4);
703     exp.tm_min = DIGIT2NUM(dp + 6);
704     exp.tm_sec = DIGIT2NUM(dp + 8);
705
706     if (apr_time_exp_gmt_get(&then, &exp) != APR_SUCCESS) {
707         return apr_pstrdup(p, "0");
708     }
709
710     diff = (long)((apr_time_sec(then) - apr_time_sec(now)) / (60*60*24));
711
712     return diff > 0 ? apr_ltoa(p, diff) : apr_pstrdup(p, "0");
713 }
714
715 static char *ssl_var_lookup_ssl_cert_serial(apr_pool_t *p, X509 *xs)
716 {
717     char *result;
718     BIO *bio;
719     int n;
720
721     if ((bio = BIO_new(BIO_s_mem())) == NULL)
722         return NULL;
723     i2a_ASN1_INTEGER(bio, X509_get_serialNumber(xs));
724     n = BIO_pending(bio);
725     result = apr_pcalloc(p, n+1);
726     n = BIO_read(bio, result, n);
727     result[n] = NUL;
728     BIO_free(bio);
729     return result;
730 }
731
732 static char *ssl_var_lookup_ssl_cert_chain(apr_pool_t *p, STACK_OF(X509) *sk, char *var)
733 {
734     char *result;
735     X509 *xs;
736     int n;
737
738     result = NULL;
739
740     if (strspn(var, "0123456789") == strlen(var)) {
741         n = atoi(var);
742         if (n < sk_X509_num(sk)) {
743             xs = sk_X509_value(sk, n);
744             result = ssl_var_lookup_ssl_cert_PEM(p, xs);
745         }
746     }
747
748     return result;
749 }
750
751 static char *ssl_var_lookup_ssl_cert_rfc4523_cea(apr_pool_t *p, SSL *ssl)
752 {
753     char *result;
754     X509 *xs;
755
756     ASN1_INTEGER *serialNumber;
757
758     if (!(xs = SSL_get_peer_certificate(ssl))) {
759         return NULL;
760     }
761
762     result = NULL;
763
764     serialNumber = X509_get_serialNumber(xs);
765     if (serialNumber) {
766         X509_NAME *issuer = X509_get_issuer_name(xs);
767         if (issuer) {
768             BIGNUM *bn = ASN1_INTEGER_to_BN(serialNumber, NULL);
769             char *decimal = BN_bn2dec(bn);
770             result = apr_pstrcat(p, "{ serialNumber ", decimal,
771                     ", issuer rdnSequence:\"",
772                     modssl_X509_NAME_to_string(p, issuer, 0), "\" }", NULL);
773             OPENSSL_free(decimal);
774             BN_free(bn);
775         }
776     }
777
778     X509_free(xs);
779     return result;
780 }
781
782 static char *ssl_var_lookup_ssl_cert_PEM(apr_pool_t *p, X509 *xs)
783 {
784     char *result;
785     BIO *bio;
786     int n;
787
788     if ((bio = BIO_new(BIO_s_mem())) == NULL)
789         return NULL;
790     PEM_write_bio_X509(bio, xs);
791     n = BIO_pending(bio);
792     result = apr_pcalloc(p, n+1);
793     n = BIO_read(bio, result, n);
794     result[n] = NUL;
795     BIO_free(bio);
796     return result;
797 }
798
799 static char *ssl_var_lookup_ssl_cert_verify(apr_pool_t *p, conn_rec *c)
800 {
801     SSLConnRec *sslconn = myConnConfig(c);
802     char *result;
803     long vrc;
804     const char *verr;
805     const char *vinfo;
806     SSL *ssl;
807     X509 *xs;
808
809     result = NULL;
810     ssl   = sslconn->ssl;
811     verr  = sslconn->verify_error;
812     vinfo = sslconn->verify_info;
813     vrc   = SSL_get_verify_result(ssl);
814     xs    = SSL_get_peer_certificate(ssl);
815
816     if (vrc == X509_V_OK && verr == NULL && xs == NULL)
817         /* no client verification done at all */
818         result = "NONE";
819     else if (vrc == X509_V_OK && verr == NULL && vinfo == NULL && xs != NULL)
820         /* client verification done successful */
821         result = "SUCCESS";
822     else if (vrc == X509_V_OK && vinfo != NULL && strEQ(vinfo, "GENEROUS"))
823         /* client verification done in generous way */
824         result = "GENEROUS";
825     else
826         /* client verification failed */
827         result = apr_psprintf(p, "FAILED:%s",
828                               verr ? verr : X509_verify_cert_error_string(vrc));
829
830     if (xs)
831         X509_free(xs);
832     return result;
833 }
834
835 static char *ssl_var_lookup_ssl_cipher(apr_pool_t *p, conn_rec *c, char *var)
836 {
837     SSLConnRec *sslconn = myConnConfig(c);
838     char *result;
839     BOOL resdup;
840     int usekeysize, algkeysize;
841     SSL *ssl;
842
843     result = NULL;
844     resdup = TRUE;
845
846     ssl = sslconn->ssl;
847     ssl_var_lookup_ssl_cipher_bits(ssl, &usekeysize, &algkeysize);
848
849     if (ssl && strEQ(var, "")) {
850         MODSSL_SSL_CIPHER_CONST SSL_CIPHER *cipher = SSL_get_current_cipher(ssl);
851         result = (cipher != NULL ? (char *)SSL_CIPHER_get_name(cipher) : NULL);
852     }
853     else if (strcEQ(var, "_EXPORT"))
854         result = (usekeysize < 56 ? "true" : "false");
855     else if (strcEQ(var, "_USEKEYSIZE")) {
856         result = apr_itoa(p, usekeysize);
857         resdup = FALSE;
858     }
859     else if (strcEQ(var, "_ALGKEYSIZE")) {
860         result = apr_itoa(p, algkeysize);
861         resdup = FALSE;
862     }
863
864     if (result != NULL && resdup)
865         result = apr_pstrdup(p, result);
866     return result;
867 }
868
869 static void ssl_var_lookup_ssl_cipher_bits(SSL *ssl, int *usekeysize, int *algkeysize)
870 {
871     MODSSL_SSL_CIPHER_CONST SSL_CIPHER *cipher;
872
873     *usekeysize = 0;
874     *algkeysize = 0;
875     if (ssl != NULL)
876         if ((cipher = SSL_get_current_cipher(ssl)) != NULL)
877             *usekeysize = SSL_CIPHER_get_bits(cipher, algkeysize);
878     return;
879 }
880
881 static char *ssl_var_lookup_ssl_version(apr_pool_t *p, char *var)
882 {
883     if (strEQ(var, "INTERFACE")) {
884         return apr_pstrdup(p, var_interface);
885     }
886     else if (strEQ(var, "LIBRARY_INTERFACE")) {
887         return apr_pstrdup(p, var_library_interface);
888     }
889     else if (strEQ(var, "LIBRARY")) {
890         return apr_pstrdup(p, var_library);
891     }
892     return NULL;
893 }
894
895 /* Add each RDN in 'xn' to the table 't' where the NID is present in
896  * 'nids', using key prefix 'pfx'.  */
897 static void extract_dn(apr_table_t *t, apr_hash_t *nids, const char *pfx,
898                        X509_NAME *xn, apr_pool_t *p)
899 {
900     STACK_OF(X509_NAME_ENTRY) *ents = xn->entries;
901     X509_NAME_ENTRY *xsne;
902     apr_hash_t *count;
903     int i, nid;
904
905     /* Hash of (int) NID -> (int *) counter to count each time an RDN
906      * with the given NID has been seen. */
907     count = apr_hash_make(p);
908
909     /* For each RDN... */
910     for (i = 0; i < sk_X509_NAME_ENTRY_num(ents); i++) {
911          const char *tag;
912
913          xsne = sk_X509_NAME_ENTRY_value(ents, i);
914
915          /* Retrieve the nid, and check whether this is one of the nids
916           * which are to be extracted. */
917          nid = OBJ_obj2nid((ASN1_OBJECT *)X509_NAME_ENTRY_get_object(xsne));
918
919          tag = apr_hash_get(nids, &nid, sizeof nid);
920          if (tag) {
921              const char *key;
922              int *dup;
923              char *value;
924
925              /* Check whether a variable with this nid was already
926               * been used; if so, use the foo_N=bar syntax. */
927              dup = apr_hash_get(count, &nid, sizeof nid);
928              if (dup) {
929                  key = apr_psprintf(p, "%s%s_%d", pfx, tag, ++(*dup));
930              }
931              else {
932                  /* Otherwise, use the plain foo=bar syntax. */
933                  dup = apr_pcalloc(p, sizeof *dup);
934                  apr_hash_set(count, &nid, sizeof nid, dup);
935                  key = apr_pstrcat(p, pfx, tag, NULL);
936              }
937              value = modssl_X509_NAME_ENTRY_to_string(p, xsne);
938              apr_table_setn(t, key, value);
939          }
940     }
941 }
942
943 void modssl_var_extract_dns(apr_table_t *t, SSL *ssl, apr_pool_t *p)
944 {
945     apr_hash_t *nids;
946     unsigned n;
947     X509 *xs;
948
949     /* Build up a hash table of (int *)NID->(char *)short-name for all
950      * the tags which are to be extracted: */
951     nids = apr_hash_make(p);
952     for (n = 0; ssl_var_lookup_ssl_cert_dn_rec[n].name; n++) {
953         if (ssl_var_lookup_ssl_cert_dn_rec[n].extract) {
954             apr_hash_set(nids, &ssl_var_lookup_ssl_cert_dn_rec[n].nid,
955                          sizeof(ssl_var_lookup_ssl_cert_dn_rec[0].nid),
956                          ssl_var_lookup_ssl_cert_dn_rec[n].name);
957         }
958     }
959
960     /* Extract the server cert DNS -- note that the refcount does NOT
961      * increase: */
962     xs = SSL_get_certificate(ssl);
963     if (xs) {
964         extract_dn(t, nids, "SSL_SERVER_S_DN_", X509_get_subject_name(xs), p);
965         extract_dn(t, nids, "SSL_SERVER_I_DN_", X509_get_issuer_name(xs), p);
966     }
967
968     /* Extract the client cert DNs -- note that the refcount DOES
969      * increase: */
970     xs = SSL_get_peer_certificate(ssl);
971     if (xs) {
972         extract_dn(t, nids, "SSL_CLIENT_S_DN_", X509_get_subject_name(xs), p);
973         extract_dn(t, nids, "SSL_CLIENT_I_DN_", X509_get_issuer_name(xs), p);
974         X509_free(xs);
975     }
976 }
977
978 static void extract_san_array(apr_table_t *t, const char *pfx,
979                               apr_array_header_t *entries, apr_pool_t *p)
980 {
981     int i;
982
983     for (i = 0; i < entries->nelts; i++) {
984         const char *key = apr_psprintf(p, "%s_%d", pfx, i);
985         apr_table_setn(t, key, APR_ARRAY_IDX(entries, i, const char *));
986     }
987 }
988
989 void modssl_var_extract_san_entries(apr_table_t *t, SSL *ssl, apr_pool_t *p)
990 {
991     X509 *xs;
992     apr_array_header_t *entries;
993
994     /* subjectAltName entries of the server certificate */
995     xs = SSL_get_certificate(ssl);
996     if (xs) {
997         if (modssl_X509_getSAN(p, xs, GEN_EMAIL, NULL, -1, &entries)) {
998             extract_san_array(t, "SSL_SERVER_SAN_Email", entries, p);
999         }
1000         if (modssl_X509_getSAN(p, xs, GEN_DNS, NULL, -1, &entries)) {
1001             extract_san_array(t, "SSL_SERVER_SAN_DNS", entries, p);
1002         }
1003         if (modssl_X509_getSAN(p, xs, GEN_OTHERNAME, "id-on-dnsSRV", -1,
1004                                &entries)) {
1005             extract_san_array(t, "SSL_SERVER_SAN_OTHER_dnsSRV", entries, p);
1006         }
1007         /* no need to free xs (refcount does not increase) */
1008     }
1009
1010     /* subjectAltName entries of the client certificate */
1011     xs = SSL_get_peer_certificate(ssl);
1012     if (xs) {
1013         if (modssl_X509_getSAN(p, xs, GEN_EMAIL, NULL, -1, &entries)) {
1014             extract_san_array(t, "SSL_CLIENT_SAN_Email", entries, p);
1015         }
1016         if (modssl_X509_getSAN(p, xs, GEN_DNS, NULL, -1, &entries)) {
1017             extract_san_array(t, "SSL_CLIENT_SAN_DNS", entries, p);
1018         }
1019         if (modssl_X509_getSAN(p, xs, GEN_OTHERNAME, "msUPN", -1, &entries)) {
1020             extract_san_array(t, "SSL_CLIENT_SAN_OTHER_msUPN", entries, p);
1021         }
1022         X509_free(xs);
1023     }
1024 }
1025
1026 /* For an extension type which OpenSSL does not recognize, attempt to
1027  * parse the extension type as a primitive string.  This will fail for
1028  * any structured extension type per the docs.  Returns non-zero on
1029  * success and writes the string to the given bio. */
1030 static int dump_extn_value(BIO *bio, ASN1_OCTET_STRING *str)
1031 {
1032     const unsigned char *pp = str->data;
1033     ASN1_STRING *ret = ASN1_STRING_new();
1034     int rv = 0;
1035
1036     /* This allows UTF8String, IA5String, VisibleString, or BMPString;
1037      * conversion to UTF-8 is forced. */
1038     if (d2i_DISPLAYTEXT(&ret, &pp, str->length)) {
1039         ASN1_STRING_print_ex(bio, ret, ASN1_STRFLGS_UTF8_CONVERT);
1040         rv = 1;
1041     }
1042
1043     ASN1_STRING_free(ret);
1044     return rv;
1045 }
1046
1047 apr_array_header_t *ssl_ext_list(apr_pool_t *p, conn_rec *c, int peer,
1048                                  const char *extension)
1049 {
1050     SSLConnRec *sslconn = myConnConfig(c);
1051     SSL *ssl = NULL;
1052     apr_array_header_t *array = NULL;
1053     X509 *xs = NULL;
1054     ASN1_OBJECT *oid = NULL;
1055     int count = 0, j;
1056
1057     if (!sslconn || !sslconn->ssl || !extension) {
1058         return NULL;
1059     }
1060     ssl = sslconn->ssl;
1061
1062     /* We accept the "extension" string to be converted as
1063      * a long name (nsComment), short name (DN) or
1064      * numeric OID (1.2.3.4).
1065      */
1066     oid = OBJ_txt2obj(extension, 0);
1067     if (!oid) {
1068         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01970)
1069                       "could not parse OID '%s'", extension);
1070         ERR_clear_error();
1071         return NULL;
1072     }
1073
1074     xs = peer ? SSL_get_peer_certificate(ssl) : SSL_get_certificate(ssl);
1075     if (xs == NULL) {
1076         return NULL;
1077     }
1078
1079     count = X509_get_ext_count(xs);
1080     /* Create an array large enough to accomodate every extension. This is
1081      * likely overkill, but safe.
1082      */
1083     array = apr_array_make(p, count, sizeof(char *));
1084     for (j = 0; j < count; j++) {
1085         X509_EXTENSION *ext = X509_get_ext(xs, j);
1086
1087         if (OBJ_cmp(ext->object, oid) == 0) {
1088             BIO *bio = BIO_new(BIO_s_mem());
1089
1090             /* We want to obtain a string representation of the extensions
1091              * value and add it to the array we're building.
1092              * X509V3_EXT_print() doesn't know about all the possible
1093              * data types, but the value is stored as an ASN1_OCTET_STRING
1094              * allowing us a fallback in case of X509V3_EXT_print
1095              * not knowing how to handle the data.
1096              */
1097             if (X509V3_EXT_print(bio, ext, 0, 0) == 1 ||
1098                 dump_extn_value(bio, X509_EXTENSION_get_data(ext)) == 1) {
1099                 BUF_MEM *buf;
1100                 char **ptr = apr_array_push(array);
1101                 BIO_get_mem_ptr(bio, &buf);
1102                 *ptr = apr_pstrmemdup(p, buf->data, buf->length);
1103             } else {
1104                 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01971)
1105                               "Found an extension '%s', but failed to "
1106                               "create a string from it", extension);
1107             }
1108             BIO_vfree(bio);
1109         }
1110     }
1111
1112     if (array->nelts == 0)
1113         array = NULL;
1114
1115     if (peer) {
1116         /* only SSL_get_peer_certificate raises the refcount */
1117         X509_free(xs);
1118     }
1119
1120     ASN1_OBJECT_free(oid);
1121     ERR_clear_error();
1122     return array;
1123 }
1124
1125 static char *ssl_var_lookup_ssl_compress_meth(SSL *ssl)
1126 {
1127     char *result = "NULL";
1128 #ifndef OPENSSL_NO_COMP
1129     SSL_SESSION *pSession = SSL_get_session(ssl);
1130
1131     if (pSession) {
1132 #ifdef OPENSSL_NO_SSL_INTERN
1133         switch (SSL_SESSION_get_compress_id(pSession)) {
1134 #else
1135         switch (pSession->compress_meth) {
1136 #endif
1137         case 0:
1138             /* default "NULL" already set */
1139             break;
1140
1141             /* Defined by RFC 3749, deflate is coded by "1" */
1142         case 1:
1143             result = "DEFLATE";
1144             break;
1145
1146             /* IANA assigned compression number for LZS */
1147         case 0x40:
1148             result = "LZS";
1149             break;
1150
1151         default:
1152             result = "UNKNOWN";
1153             break;
1154         }
1155     }
1156 #endif
1157     return result;
1158 }
1159
1160 /*  _________________________________________________________________
1161 **
1162 **  SSL Extension to mod_log_config
1163 **  _________________________________________________________________
1164 */
1165
1166 #include "../../modules/loggers/mod_log_config.h"
1167
1168 static const char *ssl_var_log_handler_c(request_rec *r, char *a);
1169 static const char *ssl_var_log_handler_x(request_rec *r, char *a);
1170
1171 /*
1172  * register us for the mod_log_config function registering phase
1173  * to establish %{...}c and to be able to expand %{...}x variables.
1174  */
1175 void ssl_var_log_config_register(apr_pool_t *p)
1176 {
1177     static APR_OPTIONAL_FN_TYPE(ap_register_log_handler) *log_pfn_register;
1178
1179     log_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_log_handler);
1180
1181     if (log_pfn_register) {
1182         log_pfn_register(p, "c", ssl_var_log_handler_c, 0);
1183         log_pfn_register(p, "x", ssl_var_log_handler_x, 0);
1184     }
1185     return;
1186 }
1187
1188 /*
1189  * implement the %{..}c log function
1190  * (we are the only function)
1191  */
1192 static const char *ssl_var_log_handler_c(request_rec *r, char *a)
1193 {
1194     SSLConnRec *sslconn = myConnConfig(r->connection);
1195     char *result;
1196
1197     if (sslconn == NULL || sslconn->ssl == NULL)
1198         return NULL;
1199     result = NULL;
1200     if (strEQ(a, "version"))
1201         result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_PROTOCOL");
1202     else if (strEQ(a, "cipher"))
1203         result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CIPHER");
1204     else if (strEQ(a, "subjectdn") || strEQ(a, "clientcert"))
1205         result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CLIENT_S_DN");
1206     else if (strEQ(a, "issuerdn") || strEQ(a, "cacert"))
1207         result = ssl_var_lookup(r->pool, r->server, r->connection, r, "SSL_CLIENT_I_DN");
1208     else if (strEQ(a, "errcode"))
1209         result = "-";
1210     else if (strEQ(a, "errstr"))
1211         result = (char *)sslconn->verify_error;
1212     if (result != NULL && result[0] == NUL)
1213         result = NULL;
1214     return result;
1215 }
1216
1217 /*
1218  * extend the implementation of the %{..}x log function
1219  * (there can be more functions)
1220  */
1221 static const char *ssl_var_log_handler_x(request_rec *r, char *a)
1222 {
1223     char *result;
1224
1225     result = ssl_var_lookup(r->pool, r->server, r->connection, r, a);
1226     if (result != NULL && result[0] == NUL)
1227         result = NULL;
1228     return result;
1229 }
1230
1231