source: mod_gnutls/src/gnutls_cache.c @ d036f96

asynciodebian/masterproxy-ticket
Last change on this file since d036f96 was 2d454a2, checked in by Fiona Klute <fiona.klute@…>, 3 years ago

Macro for session cache name

  • Property mode set to 100644
File size: 15.6 KB
Line 
1/*
2 *  Copyright 2004-2005 Paul Querna
3 *  Copyright 2008 Nikos Mavrogiannopoulos
4 *  Copyright 2011 Dash Shendy
5 *  Copyright 2015-2018 Fiona Klute
6 *
7 *  Licensed under the Apache License, Version 2.0 (the "License");
8 *  you may not use this file except in compliance with the License.
9 *  You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 *  Unless required by applicable law or agreed to in writing, software
14 *  distributed under the License is distributed on an "AS IS" BASIS,
15 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 *  See the License for the specific language governing permissions and
17 *  limitations under the License.
18 */
19
20/**
21 * @file gnutls_cache.c
22 *
23 * This file contains the cache implementation used for session
24 * caching and OCSP stapling. The `socache_*_session` functions
25 * implement the GnuTLS session cache API using the configured cache,
26 * using mgs_cache_store() and mgs_cache_fetch() as appropriate (see
27 * gnutls_cache.h).
28 */
29
30#include "gnutls_cache.h"
31#include "mod_gnutls.h"
32#include "gnutls_config.h"
33
34#include <ap_socache.h>
35#include <apr_escape.h>
36#include <util_mutex.h>
37
38/** Default session cache timeout */
39#define MGS_DEFAULT_CACHE_TIMEOUT 300
40
41/** Session cache name */
42#define MGS_SESSION_CACHE_NAME "gnutls_session"
43
44/** Maximum length of the hex string representation of a GnuTLS
45 * session ID: two characters per byte, plus one more for `\0` */
46#if GNUTLS_VERSION_NUMBER >= 0x030400
47#define GNUTLS_SESSION_ID_STRING_LEN ((GNUTLS_MAX_SESSION_ID_SIZE * 2) + 1)
48#else
49#define GNUTLS_SESSION_ID_STRING_LEN ((GNUTLS_MAX_SESSION_ID * 2) + 1)
50#endif
51
52#ifdef APLOG_USE_MODULE
53APLOG_USE_MODULE(gnutls);
54#endif
55
56/**
57 * Turn a GnuTLS session ID into the key format we use for
58 * caches. Name the Session ID as `server:port.SessionID` to disallow
59 * resuming sessions on different servers.
60 *
61 * @return `0` on success, `-1` on failure
62 */
63static int mgs_session_id2dbm(conn_rec *c, unsigned char *id, int idlen,
64                              gnutls_datum_t *dbmkey)
65{
66    char sz[GNUTLS_SESSION_ID_STRING_LEN];
67    apr_status_t rv = apr_escape_hex(sz, id, idlen, 0, NULL);
68    if (rv != APR_SUCCESS)
69        return -1;
70
71    char *newkey = apr_psprintf(c->pool, "%s:%d.%s",
72                                c->base_server->server_hostname,
73                                c->base_server->port, sz);
74    dbmkey->size = strlen(newkey);
75    /* signedness does not matter for arbitrary bits */
76    dbmkey->data = (unsigned char*) newkey;
77    return 0;
78}
79
80/** The OPENSSL_TIME_FORMAT macro and mgs_time2sz() serve to print
81 * time in a format compatible with OpenSSL's `ASN1_TIME_print()`
82 * function. */
83#define OPENSSL_TIME_FORMAT "%b %d %k:%M:%S %Y %Z"
84
85char *mgs_time2sz(time_t in_time, char *str, int strsize)
86{
87    apr_time_exp_t vtm;
88    apr_size_t ret_size;
89    apr_time_t t;
90
91
92    apr_time_ansi_put(&t, in_time);
93    apr_time_exp_gmt(&vtm, t);
94    apr_strftime(str, &ret_size, strsize - 1, OPENSSL_TIME_FORMAT, &vtm);
95
96    return str;
97}
98
99
100
101int mgs_cache_store(mgs_cache_t cache, server_rec *server,
102                    gnutls_datum_t key, gnutls_datum_t data,
103                    apr_time_t expiry)
104{
105    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
106        ap_get_module_config(server->module_config, &gnutls_module);
107
108    apr_pool_t *spool;
109    apr_pool_create(&spool, NULL);
110
111    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
112        apr_global_mutex_lock(cache->mutex);
113    apr_status_t rv = cache->prov->store(cache->socache, server,
114                                         key.data, key.size,
115                                         expiry,
116                                         data.data, data.size,
117                                         spool);
118    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
119        apr_global_mutex_unlock(cache->mutex);
120
121    if (rv != APR_SUCCESS)
122    {
123        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, server,
124                     "error storing in cache '%s:%s'",
125                     cache->prov->name, sc->cache->config);
126        apr_pool_destroy(spool);
127        return -1;
128    }
129
130    ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
131                 "stored %u bytes of data (%u byte key) in cache '%s:%s'",
132                 data.size, key.size,
133                 cache->prov->name, sc->cache->config);
134    apr_pool_destroy(spool);
135    return 0;
136}
137
138
139
140/**
141 * Store function for the GnuTLS session cache, see
142 * gnutls_db_set_store_function().
143 *
144 * @param baton mgs_handle_t for the connection, as set via
145 * gnutls_db_set_ptr()
146 *
147 * @param key object key to store
148 *
149 * @param data the object to store
150 *
151 * @return `0` in case of success, `-1` in case of failure
152 */
153static int socache_store_session(void *baton, gnutls_datum_t key,
154                                 gnutls_datum_t data)
155{
156    mgs_handle_t *ctxt = baton;
157    gnutls_datum_t dbmkey;
158
159    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &dbmkey) < 0)
160        return -1;
161
162    apr_time_t expiry = apr_time_now() + ctxt->sc->cache_timeout;
163
164    return mgs_cache_store(ctxt->sc->cache, ctxt->c->base_server,
165                           dbmkey, data, expiry);
166}
167
168
169
170/** 8K is the maximum size accepted when receiving OCSP responses,
171 * sessions cache entries should be much smaller. The buffer is
172 * reallocated to actual size after fetching, so memory waste is
173 * minimal and temporary. */
174#define SOCACHE_FETCH_BUF_SIZE (8 * 1024)
175
176gnutls_datum_t mgs_cache_fetch(mgs_cache_t cache, server_rec *server,
177                               gnutls_datum_t key, apr_pool_t *pool)
178{
179    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
180        ap_get_module_config(server->module_config, &gnutls_module);
181
182    gnutls_datum_t data = {NULL, 0};
183    data.data = gnutls_malloc(SOCACHE_FETCH_BUF_SIZE);
184    if (data.data == NULL)
185        return data;
186    data.size = SOCACHE_FETCH_BUF_SIZE;
187
188    apr_pool_t *spool;
189    apr_pool_create(&spool, pool);
190
191    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
192        apr_global_mutex_lock(cache->mutex);
193    apr_status_t rv = cache->prov->retrieve(cache->socache, server,
194                                            key.data, key.size,
195                                            data.data, &data.size,
196                                            spool);
197    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
198        apr_global_mutex_unlock(cache->mutex);
199
200    if (rv != APR_SUCCESS)
201    {
202        /* APR_NOTFOUND means there's no such object. */
203        if (rv == APR_NOTFOUND)
204            ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
205                         "requested entry not found in cache '%s:%s'.",
206                         cache->prov->name, sc->cache->config);
207        else
208            ap_log_error(APLOG_MARK, APLOG_WARNING, rv, server,
209                         "error fetching from cache '%s:%s'",
210                         cache->prov->name, sc->cache->config);
211        /* free unused buffer */
212        gnutls_free(data.data);
213        data.data = NULL;
214        data.size = 0;
215    }
216    else
217    {
218        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
219                     "fetched %u bytes from cache '%s:%s'",
220                     data.size, cache->prov->name, sc->cache->config);
221
222        /* Realloc buffer to data.size. Data size must be less than or
223         * equal to the initial buffer size, so this REALLY should not
224         * fail. */
225        data.data = gnutls_realloc(data.data, data.size);
226        if (__builtin_expect(data.data == NULL, 0))
227        {
228            ap_log_error(APLOG_MARK, APLOG_CRIT, APR_ENOMEM, server,
229                         "%s: Could not realloc fetch buffer to data size!",
230                         __func__);
231            data.size = 0;
232        }
233    }
234    apr_pool_destroy(spool);
235
236    return data;
237}
238
239
240
241/**
242 * Fetch function for the GnuTLS session cache, see
243 * gnutls_db_set_retrieve_function().
244 *
245 * *Warning*: The `data` element of the returned `gnutls_datum_t` is
246 * allocated using `gnutls_malloc()` for compatibility with the GnuTLS
247 * session caching API, and must be released using `gnutls_free()`.
248 *
249 * @param baton mgs_handle_t for the connection, as set via
250 * gnutls_db_set_ptr()
251 *
252 * @param key object key to fetch
253 *
254 * @return the requested cache entry, or `{NULL, 0}`
255 */
256static gnutls_datum_t socache_fetch_session(void *baton, gnutls_datum_t key)
257{
258    gnutls_datum_t data = {NULL, 0};
259    gnutls_datum_t dbmkey;
260    mgs_handle_t *ctxt = baton;
261
262    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &dbmkey) < 0)
263        return data;
264
265    return mgs_cache_fetch(ctxt->sc->cache, ctxt->c->base_server,
266                           dbmkey, ctxt->c->pool);
267}
268
269
270
271/**
272 * Remove function for the GnuTLS session cache, see
273 * gnutls_db_set_remove_function().
274 *
275 * @param baton mgs_handle_t for the connection, as set via
276 * gnutls_db_set_ptr()
277 *
278 * @param key object key to remove
279 *
280 * @return `0` in case of success, `-1` in case of failure
281 */
282static int socache_delete_session(void *baton, gnutls_datum_t key)
283{
284    gnutls_datum_t tmpkey;
285    mgs_handle_t *ctxt = baton;
286
287    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &tmpkey) < 0)
288        return -1;
289
290    if (ctxt->sc->cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
291        apr_global_mutex_lock(ctxt->sc->cache->mutex);
292    apr_status_t rv = ctxt->sc->cache->prov->remove(ctxt->sc->cache->socache,
293                                                    ctxt->c->base_server,
294                                                    key.data, key.size,
295                                                    ctxt->c->pool);
296    if (ctxt->sc->cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
297        apr_global_mutex_unlock(ctxt->sc->cache->mutex);
298
299    if (rv != APR_SUCCESS) {
300        ap_log_error(APLOG_MARK, APLOG_NOTICE, rv,
301                     ctxt->c->base_server,
302                     "error deleting from cache '%s:%s'",
303                     ctxt->sc->cache->prov->name, ctxt->sc->cache->config);
304        return -1;
305    }
306    return 0;
307}
308
309
310
311const char *mgs_cache_inst_config(mgs_cache_t *cache, server_rec *server,
312                                  const char* type, const char* config,
313                                  apr_pool_t *pconf, apr_pool_t *ptemp)
314{
315    /* allocate cache structure if needed */
316    if (*cache == NULL)
317    {
318        *cache = apr_pcalloc(pconf, sizeof(struct mgs_cache));
319        if (*cache == NULL)
320            return "Could not allocate memory for cache configuration!";
321    }
322    mgs_cache_t c = *cache;
323
324    /* Find the right socache provider */
325    c->prov = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
326                                 type,
327                                 AP_SOCACHE_PROVIDER_VERSION);
328    if (c->prov == NULL)
329    {
330        return apr_psprintf(ptemp,
331                            "Could not find socache provider '%s', please "
332                            "make sure that the provider name is valid and "
333                            "the appropriate module is loaded (maybe "
334                            "mod_socache_%s.so?).",
335                            type, type);
336    }
337
338    /* shmcb works fine with NULL, but make sure there's a valid (if
339     * empty) string for logging */
340    if (config != NULL)
341        c->config = apr_pstrdup(pconf, config);
342    else
343        c->config = "";
344
345    /* Create and configure the cache instance. */
346    const char *err = c->prov->create(&c->socache, c->config, ptemp, pconf);
347    if (err != NULL)
348    {
349        return apr_psprintf(ptemp,
350                            "Creating cache '%s:%s' failed: %s",
351                            c->prov->name, c->config, err);
352    }
353    ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, server,
354                 "%s: Socache '%s:%s' created.",
355                 __func__, c->prov->name, c->config);
356
357    return NULL;
358}
359
360
361
362/**
363 * This function is supposed to be called during post_config to
364 * initialize mutex and socache instance associated with an
365 * mgs_cache_t.
366 *
367 * @param cache the mod_gnutls cache structure
368 *
369 * @param cache_name name for socache initialization
370 *
371 * @param mutex_name name to pass to ap_global_mutex_create(), must
372 * have been registered during pre_config.
373 *
374 * @param server server for logging purposes
375 *
376 * @param pconf memory pool for server configuration
377 */
378static apr_status_t mgs_cache_inst_init(mgs_cache_t cache,
379                                        const char *cache_name,
380                                        const char *mutex_name,
381                                        server_rec *server,
382                                        apr_pool_t *pconf)
383{
384    apr_status_t rv = APR_SUCCESS;
385
386    if (cache->mutex == NULL)
387    {
388        rv = ap_global_mutex_create(&cache->mutex, NULL,
389                                    mutex_name,
390                                    NULL, server, pconf, 0);
391        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
392                     "%s: create mutex", __func__);
393        if (rv != APR_SUCCESS)
394            return rv;
395    }
396
397    rv = cache->prov->init(cache->socache, cache_name, NULL, server, pconf);
398    if (rv != APR_SUCCESS)
399        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, server,
400                     "Initializing cache '%s:%s' failed!",
401                     cache->prov->name, cache->config);
402    else
403        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, server,
404                     "%s: socache '%s:%s' initialized.", __func__,
405                     cache->prov->name, cache->config);
406    return rv;
407}
408
409
410
411static apr_status_t cleanup_socache(void *data)
412{
413    server_rec *s = data;
414    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
415        ap_get_module_config(s->module_config, &gnutls_module);
416    ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, s,
417                 "Cleaning up socache '%s:%s'",
418                 sc->cache->prov->name, sc->cache->config);
419    sc->cache->prov->destroy(sc->cache->socache, s);
420    return APR_SUCCESS;
421}
422
423
424
425int mgs_cache_post_config(apr_pool_t *pconf,
426                          apr_pool_t *ptemp __attribute__((unused)),
427                          server_rec *s, mgs_srvconf_rec *sc)
428{
429    apr_status_t rv = APR_SUCCESS;
430    /* GnuTLSCache was never explicitly set or is disabled: */
431    if (sc->cache_enable == GNUTLS_ENABLED_UNSET
432        || sc->cache_enable == GNUTLS_ENABLED_FALSE)
433    {
434        sc->cache_enable = GNUTLS_ENABLED_FALSE;
435        /* Cache disabled, done. */
436        return APR_SUCCESS;
437    }
438    /* if GnuTLSCacheTimeout was never explicitly set: */
439    if (sc->cache_timeout == MGS_TIMEOUT_UNSET)
440        sc->cache_timeout = apr_time_from_sec(MGS_DEFAULT_CACHE_TIMEOUT);
441
442    rv = mgs_cache_inst_init(sc->cache, MGS_SESSION_CACHE_NAME,
443                             MGS_CACHE_MUTEX_NAME, s, pconf);
444    if (rv != APR_SUCCESS)
445        return HTTP_INSUFFICIENT_STORAGE;
446
447    apr_pool_pre_cleanup_register(pconf, s, cleanup_socache);
448
449    return APR_SUCCESS;
450}
451
452int mgs_cache_child_init(apr_pool_t * p,
453                         server_rec * s,
454                         mgs_srvconf_rec * sc)
455{
456    /* reinit cache mutex */
457    const char *lockfile = apr_global_mutex_lockfile(sc->cache->mutex);
458    apr_status_t rv = apr_global_mutex_child_init(&sc->cache->mutex,
459                                                  lockfile, p);
460    if (rv != APR_SUCCESS)
461        ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
462                     "Failed to reinit mutex '%s'", MGS_CACHE_MUTEX_NAME);
463
464    return 0;
465}
466
467int mgs_cache_session_init(mgs_handle_t * ctxt)
468{
469    if (ctxt->sc->cache_enable)
470    {
471        gnutls_db_set_retrieve_function(ctxt->session,
472                                        socache_fetch_session);
473        gnutls_db_set_remove_function(ctxt->session,
474                                      socache_delete_session);
475        gnutls_db_set_store_function(ctxt->session,
476                                     socache_store_session);
477        gnutls_db_set_ptr(ctxt->session, ctxt);
478    }
479    return 0;
480}
Note: See TracBrowser for help on using the repository browser.