source: mod_gnutls/src/gnutls_cache.c @ 2f932fa

debian/masterdebian/stretch-backportsupstream
Last change on this file since 2f932fa was 2f932fa, checked in by Thomas Klute <thomas2.klute@…>, 4 years ago

Use gnutls_datum_t to pass DBM keys for GnuTLS sessions

The APR memcached client interface does not use apr_datum_t. This
change makes it easier to keep function signatures identical among the
mod_gnutls cache functions.

  • Property mode set to 100644
File size: 19.1 KB
Line 
1/**
2 *  Copyright 2004-2005 Paul Querna
3 *  Copyright 2008 Nikos Mavrogiannopoulos
4 *  Copyright 2011 Dash Shendy
5 *  Copyright 2015-2016 Thomas 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#include "gnutls_cache.h"
22#include "mod_gnutls.h"
23
24#if HAVE_APR_MEMCACHE
25#include "apr_memcache.h"
26#endif
27
28#include "apr_dbm.h"
29
30#include "ap_mpm.h"
31
32#include <unistd.h>
33#include <sys/types.h>
34
35#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
36#include "unixd.h"
37#endif
38
39/* it seems the default has some strange errors. Use SDBM
40 */
41#define MC_TAG "mod_gnutls:"
42#define MC_TAG_LEN sizeof(MC_TAG)
43#define STR_SESSION_LEN (GNUTLS_SESSION_ID_STRING_LEN + MC_TAG_LEN)
44
45#if MODULE_MAGIC_NUMBER_MAJOR < 20081201
46#define ap_unixd_config unixd_config
47#endif
48
49#ifdef APLOG_USE_MODULE
50APLOG_USE_MODULE(gnutls);
51#endif
52
53char *mgs_session_id2sz(unsigned char *id, int idlen,
54        char *str, int strsize) {
55    char *cp;
56    int n;
57
58    cp = str;
59    for (n = 0; n < idlen && n < GNUTLS_MAX_SESSION_ID; n++) {
60        apr_snprintf(cp, strsize - (cp - str), "%02X", id[n]);
61        cp += 2;
62    }
63    *cp = '\0';
64    return str;
65}
66
67/* Name the Session ID as:
68 * server:port.SessionID
69 * to disallow resuming sessions on different servers
70 */
71static int mgs_session_id2dbm(conn_rec *c, unsigned char *id, int idlen,
72                              gnutls_datum_t *dbmkey)
73{
74    char buf[STR_SESSION_LEN];
75    char *sz;
76
77    sz = mgs_session_id2sz(id, idlen, buf, sizeof (buf));
78    if (sz == NULL)
79        return -1;
80
81    char *newkey = apr_psprintf(c->pool, "%s:%d.%s",
82                                c->base_server->server_hostname,
83                                c->base_server->port, sz);
84    dbmkey->size = strlen(newkey);
85    /* signedness does not matter for arbitrary bits */
86    dbmkey->data = (unsigned char*) newkey;
87    return 0;
88}
89
90#define CTIME "%b %d %k:%M:%S %Y %Z"
91
92char *mgs_time2sz(time_t in_time, char *str, int strsize) {
93    apr_time_exp_t vtm;
94    apr_size_t ret_size;
95    apr_time_t t;
96
97
98    apr_time_ansi_put(&t, in_time);
99    apr_time_exp_gmt(&vtm, t);
100    apr_strftime(str, &ret_size, strsize - 1, CTIME, &vtm);
101
102    return str;
103}
104
105#if HAVE_APR_MEMCACHE
106
107/* Name the Session ID as:
108 * server:port.SessionID
109 * to disallow resuming sessions on different servers
110 */
111static char *mgs_session_id2mc(conn_rec * c, unsigned char *id, int idlen) {
112    char buf[STR_SESSION_LEN];
113    char *sz;
114
115    sz = mgs_session_id2sz(id, idlen, buf, sizeof (buf));
116    if (sz == NULL)
117        return NULL;
118
119    return apr_psprintf(c->pool, MC_TAG "%s:%d.%s",
120            c->base_server->server_hostname,
121            c->base_server->port, sz);
122}
123
124/**
125 * GnuTLS Session Cache using libmemcached
126 *
127 */
128
129/* The underlying apr_memcache system is thread safe... woohoo */
130static apr_memcache_t *mc;
131
132static int mc_cache_child_init(apr_pool_t * p, server_rec * s,
133        mgs_srvconf_rec * sc) {
134    apr_status_t rv = APR_SUCCESS;
135    int thread_limit = 0;
136    int nservers = 0;
137    char *cache_config;
138    char *split;
139    char *tok;
140
141    ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit);
142
143    /* Find all the servers in the first run to get a total count */
144    cache_config = apr_pstrdup(p, sc->cache_config);
145    split = apr_strtok(cache_config, " ", &tok);
146    while (split) {
147        nservers++;
148        split = apr_strtok(NULL, " ", &tok);
149    }
150
151    rv = apr_memcache_create(p, nservers, 0, &mc);
152    if (rv != APR_SUCCESS) {
153        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
154                "[gnutls_cache] Failed to create Memcache Object of '%d' size.",
155                nservers);
156        return rv;
157    }
158
159    /* Now add each server to the memcache */
160    cache_config = apr_pstrdup(p, sc->cache_config);
161    split = apr_strtok(cache_config, " ", &tok);
162    while (split) {
163        apr_memcache_server_t *st;
164        char *host_str;
165        char *scope_id;
166        apr_port_t port;
167
168        rv = apr_parse_addr_port(&host_str, &scope_id, &port,
169                split, p);
170        if (rv != APR_SUCCESS) {
171            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
172                    "[gnutls_cache] Failed to Parse Server: '%s'",
173                    split);
174            return rv;
175        }
176
177        if (host_str == NULL) {
178            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
179                    "[gnutls_cache] Failed to Parse Server, "
180                    "no hostname specified: '%s'", split);
181            return rv;
182        }
183
184        if (port == 0) {
185            port = 11211; /* default port */
186        }
187
188        /* Should Max Conns be (thread_limit / nservers) ? */
189        rv = apr_memcache_server_create(p,
190                host_str, port,
191                0,
192                1, thread_limit, 600, &st);
193        if (rv != APR_SUCCESS) {
194            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
195                    "[gnutls_cache] Failed to Create Server: %s:%d",
196                    host_str, port);
197            return rv;
198        }
199
200        rv = apr_memcache_add_server(mc, st);
201        if (rv != APR_SUCCESS) {
202            ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
203                    "[gnutls_cache] Failed to Add Server: %s:%d",
204                    host_str, port);
205            return rv;
206        }
207
208        split = apr_strtok(NULL, " ", &tok);
209    }
210    return rv;
211}
212
213static int mc_cache_store(void *baton, gnutls_datum_t key,
214        gnutls_datum_t data) {
215    apr_status_t rv = APR_SUCCESS;
216    mgs_handle_t *ctxt = baton;
217    char *strkey = NULL;
218    apr_uint32_t timeout;
219
220    strkey = mgs_session_id2mc(ctxt->c, key.data, key.size);
221    if (!strkey)
222        return -1;
223
224    timeout = apr_time_sec(ctxt->sc->cache_timeout);
225
226    rv = apr_memcache_set(mc, strkey, (char *) data.data, data.size, timeout,
227            0);
228
229    if (rv != APR_SUCCESS) {
230        ap_log_error(APLOG_MARK, APLOG_CRIT, rv,
231                ctxt->c->base_server,
232                "[gnutls_cache] error setting key '%s' "
233                "with %d bytes of data", strkey, data.size);
234        return -1;
235    }
236
237    return 0;
238}
239
240static gnutls_datum_t mc_cache_fetch(void *baton, gnutls_datum_t key) {
241    apr_status_t rv = APR_SUCCESS;
242    mgs_handle_t *ctxt = baton;
243    char *strkey = NULL;
244    char *value;
245    apr_size_t value_len;
246    gnutls_datum_t data = {NULL, 0};
247
248    strkey = mgs_session_id2mc(ctxt->c, key.data, key.size);
249    if (!strkey) {
250        return data;
251    }
252
253    rv = apr_memcache_getp(mc, ctxt->c->pool, strkey,
254            &value, &value_len, NULL);
255
256    if (rv != APR_SUCCESS) {
257#if MOD_GNUTLS_DEBUG
258        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv,
259                ctxt->c->base_server,
260                "[gnutls_cache] error fetching key '%s' ",
261                strkey);
262#endif
263        data.size = 0;
264        data.data = NULL;
265        return data;
266    }
267
268    /* TODO: Eliminate this memcpy. gnutls-- */
269    data.data = gnutls_malloc(value_len);
270    if (data.data == NULL)
271        return data;
272
273    data.size = value_len;
274    memcpy(data.data, value, value_len);
275
276    return data;
277}
278
279static int mc_cache_delete(void *baton, gnutls_datum_t key) {
280    apr_status_t rv = APR_SUCCESS;
281    mgs_handle_t *ctxt = baton;
282    char *strkey = NULL;
283
284    strkey = mgs_session_id2mc(ctxt->c, key.data, key.size);
285    if (!strkey)
286        return -1;
287
288    rv = apr_memcache_delete(mc, strkey, 0);
289
290    if (rv != APR_SUCCESS) {
291        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv,
292                ctxt->c->base_server,
293                "[gnutls_cache] error deleting key '%s' ",
294                strkey);
295        return -1;
296    }
297
298    return 0;
299}
300
301#endif  /* have_apr_memcache */
302
303static const char *db_type(mgs_srvconf_rec * sc) {
304    if (sc->cache_type == mgs_cache_gdbm)
305        return "gdbm";
306    else
307        return "db";
308}
309
310#define SSL_DBM_FILE_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
311
312/***
313 * The signatures of the dbm_cache_...() functions may be a bit
314 * confusing: "store" and "expire" take a server_rec, "fetch" an
315 * mgs_handle_t, and "delete" the void* required for a
316 * gnutls_db_remove_func. The first three have matching ..._session
317 * functions to fit their respective GnuTLS session cache signatures.
318 *
319 * This is because "store", "expire", and "fetch" are also needed for
320 * the OCSP cache. Their ..._session variants have been created to
321 * take care of the session cache specific parts, mainly calculating
322 * the DB key from the session ID. They have to match the appropriate
323 * GnuTLS DB function signatures.
324 *
325 * To update cached OCSP responses independent of client connections,
326 * "store" and "expire" have to work without a connection context. On
327 * the other hand "fetch" does not need to do that, because cached
328 * OCSP responses will be retrieved for use in client connections.
329 ***/
330
331static void dbm_cache_expire(server_rec *s)
332{
333    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
334        ap_get_module_config(s->module_config, &gnutls_module);
335
336    apr_status_t rv;
337    apr_dbm_t *dbm;
338    apr_datum_t dbmkey;
339    apr_datum_t dbmval;
340    apr_time_t dtime;
341    apr_pool_t *spool;
342    int total, deleted;
343
344    apr_time_t now = apr_time_now();
345
346    if (now - sc->last_cache_check < (sc->cache_timeout) / 2)
347        return;
348
349    sc->last_cache_check = now;
350
351    apr_pool_create(&spool, NULL);
352
353    total = 0;
354    deleted = 0;
355
356    rv = apr_dbm_open_ex(&dbm, db_type(sc),
357            sc->cache_config, APR_DBM_RWCREATE,
358            SSL_DBM_FILE_MODE, spool);
359    if (rv != APR_SUCCESS) {
360        ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, s,
361                "[gnutls_cache] error opening cache searcher '%s'",
362                sc->cache_config);
363        apr_pool_destroy(spool);
364        return;
365    }
366
367    apr_dbm_firstkey(dbm, &dbmkey);
368    while (dbmkey.dptr != NULL) {
369        apr_dbm_fetch(dbm, dbmkey, &dbmval);
370        if (dbmval.dptr != NULL
371                && dbmval.dsize >= sizeof (apr_time_t)) {
372            memcpy(&dtime, dbmval.dptr, sizeof (apr_time_t));
373
374            if (now >= dtime) {
375                apr_dbm_delete(dbm, dbmkey);
376                deleted++;
377            }
378            apr_dbm_freedatum(dbm, dbmval);
379        } else {
380            apr_dbm_delete(dbm, dbmkey);
381            deleted++;
382        }
383        total++;
384        apr_dbm_nextkey(dbm, &dbmkey);
385    }
386    apr_dbm_close(dbm);
387
388    ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s,
389            "[gnutls_cache] Cleaned up cache '%s'. Deleted %d and left %d",
390            sc->cache_config, deleted, total - deleted);
391
392    apr_pool_destroy(spool);
393
394    return;
395}
396
397static gnutls_datum_t dbm_cache_fetch(mgs_handle_t *ctxt, gnutls_datum_t key)
398{
399    gnutls_datum_t data = {NULL, 0};
400    apr_dbm_t *dbm;
401    apr_datum_t dbmkey = {(char*) key.data, key.size};
402    apr_datum_t dbmval;
403    apr_status_t rv;
404
405    rv = apr_dbm_open_ex(&dbm, db_type(ctxt->sc),
406            ctxt->sc->cache_config, APR_DBM_READONLY,
407            SSL_DBM_FILE_MODE, ctxt->c->pool);
408    if (rv != APR_SUCCESS) {
409        ap_log_cerror(APLOG_MARK, APLOG_NOTICE, rv, ctxt->c,
410                      "error opening cache '%s'",
411                      ctxt->sc->cache_config);
412        return data;
413    }
414
415    rv = apr_dbm_fetch(dbm, dbmkey, &dbmval);
416
417    if (rv != APR_SUCCESS) {
418        apr_dbm_close(dbm);
419        return data;
420    }
421
422    if (dbmval.dptr == NULL || dbmval.dsize <= sizeof (apr_time_t)) {
423        apr_dbm_freedatum(dbm, dbmval);
424        apr_dbm_close(dbm);
425        return data;
426    }
427
428    data.size = dbmval.dsize - sizeof (apr_time_t);
429
430    data.data = gnutls_malloc(data.size);
431    if (data.data == NULL) {
432        apr_dbm_freedatum(dbm, dbmval);
433        apr_dbm_close(dbm);
434        return data;
435    }
436
437    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, ctxt->c,
438                  "fetched %ld bytes from cache",
439                  dbmval.dsize);
440
441    memcpy(data.data, dbmval.dptr + sizeof (apr_time_t), data.size);
442
443    apr_dbm_freedatum(dbm, dbmval);
444    apr_dbm_close(dbm);
445
446    return data;
447}
448
449static gnutls_datum_t dbm_cache_fetch_session(void *baton, gnutls_datum_t key)
450{
451    gnutls_datum_t data = {NULL, 0};
452    gnutls_datum_t dbmkey;
453    mgs_handle_t *ctxt = baton;
454
455    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &dbmkey) < 0)
456        return data;
457
458    return dbm_cache_fetch(ctxt, dbmkey);
459}
460
461static int dbm_cache_store(server_rec *s, gnutls_datum_t key,
462                           gnutls_datum_t data, apr_time_t expiry)
463{
464    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
465        ap_get_module_config(s->module_config, &gnutls_module);
466
467    apr_dbm_t *dbm;
468    apr_datum_t dbmkey = {(char*) key.data, key.size};
469    apr_datum_t dbmval;
470    apr_status_t rv;
471    apr_pool_t *spool;
472
473    /* we expire dbm only on every store */
474    dbm_cache_expire(s);
475
476    apr_pool_create(&spool, NULL);
477
478    /* create DBM value */
479    dbmval.dsize = data.size + sizeof (apr_time_t);
480    dbmval.dptr = (char *) apr_palloc(spool, dbmval.dsize);
481
482    /* prepend expiration time */
483    memcpy((char *) dbmval.dptr, &expiry, sizeof (apr_time_t));
484    memcpy((char *) dbmval.dptr + sizeof (apr_time_t),
485            data.data, data.size);
486
487    rv = apr_dbm_open_ex(&dbm, db_type(sc),
488                         sc->cache_config, APR_DBM_RWCREATE,
489                         SSL_DBM_FILE_MODE, spool);
490    if (rv != APR_SUCCESS)
491    {
492        ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, s,
493                     "error opening cache '%s'",
494                     sc->cache_config);
495        apr_pool_destroy(spool);
496        return -1;
497    }
498
499    rv = apr_dbm_store(dbm, dbmkey, dbmval);
500    if (rv != APR_SUCCESS)
501    {
502        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s,
503                     "error storing in cache '%s'",
504                     sc->cache_config);
505        apr_dbm_close(dbm);
506        apr_pool_destroy(spool);
507        return -1;
508    }
509
510    ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s,
511                 "stored %ld bytes of data (%ld byte key) in cache '%s'",
512                 dbmval.dsize, dbmkey.dsize, sc->cache_config);
513
514    apr_dbm_close(dbm);
515
516    apr_pool_destroy(spool);
517
518    return 0;
519}
520
521static int dbm_cache_store_session(void *baton, gnutls_datum_t key,
522                                   gnutls_datum_t data)
523{
524    mgs_handle_t *ctxt = baton;
525    gnutls_datum_t dbmkey;
526
527    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &dbmkey) < 0)
528        return -1;
529
530    apr_time_t expiry = apr_time_now() + ctxt->sc->cache_timeout;
531
532    return dbm_cache_store(ctxt->c->base_server, dbmkey, data, expiry);
533}
534
535static int dbm_cache_delete(void *baton, gnutls_datum_t key) {
536    apr_dbm_t *dbm;
537    gnutls_datum_t tmpkey;
538    mgs_handle_t *ctxt = baton;
539    apr_status_t rv;
540
541    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &tmpkey) < 0)
542        return -1;
543    apr_datum_t dbmkey = {(char*) tmpkey.data, tmpkey.size};
544
545    rv = apr_dbm_open_ex(&dbm, db_type(ctxt->sc),
546            ctxt->sc->cache_config, APR_DBM_RWCREATE,
547            SSL_DBM_FILE_MODE, ctxt->c->pool);
548    if (rv != APR_SUCCESS) {
549        ap_log_error(APLOG_MARK, APLOG_NOTICE, rv,
550                ctxt->c->base_server,
551                "[gnutls_cache] error opening cache '%s'",
552                ctxt->sc->cache_config);
553        return -1;
554    }
555
556    rv = apr_dbm_delete(dbm, dbmkey);
557
558    if (rv != APR_SUCCESS) {
559        ap_log_error(APLOG_MARK, APLOG_NOTICE, rv,
560                ctxt->c->base_server,
561                "[gnutls_cache] error deleting from cache '%s'",
562                ctxt->sc->cache_config);
563        apr_dbm_close(dbm);
564        return -1;
565    }
566
567    apr_dbm_close(dbm);
568
569    return 0;
570}
571
572static int dbm_cache_post_config(apr_pool_t * p, server_rec * s,
573        mgs_srvconf_rec * sc) {
574    apr_status_t rv;
575    apr_dbm_t *dbm;
576    const char *path1;
577    const char *path2;
578
579    rv = apr_dbm_open_ex(&dbm, db_type(sc), sc->cache_config,
580            APR_DBM_RWCREATE, SSL_DBM_FILE_MODE, p);
581
582    if (rv != APR_SUCCESS) {
583        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
584                "GnuTLS: Cannot create DBM Cache at `%s'",
585                sc->cache_config);
586        return rv;
587    }
588
589    apr_dbm_close(dbm);
590
591    apr_dbm_get_usednames_ex(p, db_type(sc), sc->cache_config, &path1,
592            &path2);
593
594    /* The Following Code takes logic directly from mod_ssl's DBM Cache */
595#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
596    /* Running as Root */
597    if (path1 && geteuid() == 0) {
598        if (0 != chown(path1, ap_unixd_config.user_id, -1))
599            ap_log_error(APLOG_MARK, APLOG_NOTICE, -1, s,
600                         "GnuTLS: could not chown cache path1 `%s' to uid %d (errno: %d)",
601                         path1, ap_unixd_config.user_id, errno);
602        if (path2 != NULL) {
603            if (0 != chown(path2, ap_unixd_config.user_id, -1))
604                ap_log_error(APLOG_MARK, APLOG_NOTICE, -1, s,
605                             "GnuTLS: could not chown cache path2 `%s' to uid %d (errno: %d)",
606                             path2, ap_unixd_config.user_id, errno);
607        }
608    }
609#endif
610
611    return rv;
612}
613
614int mgs_cache_post_config(apr_pool_t * p, server_rec * s,
615        mgs_srvconf_rec * sc) {
616
617    /* if GnuTLSCache was never explicitly set: */
618    if (sc->cache_type == mgs_cache_unset)
619        sc->cache_type = mgs_cache_none;
620    /* if GnuTLSCacheTimeout was never explicitly set: */
621    if (sc->cache_timeout == -1)
622        sc->cache_timeout = apr_time_from_sec(300);
623
624    if (sc->cache_type == mgs_cache_dbm
625            || sc->cache_type == mgs_cache_gdbm) {
626        return dbm_cache_post_config(p, s, sc);
627    }
628    return 0;
629}
630
631#if HAVE_APR_MEMCACHE
632int mgs_cache_child_init(apr_pool_t * p,
633                         server_rec * s,
634                         mgs_srvconf_rec * sc)
635#else
636int mgs_cache_child_init(apr_pool_t * p __attribute__((unused)),
637                         server_rec * s __attribute__((unused)),
638                         mgs_srvconf_rec * sc)
639#endif
640{
641    if (sc->cache_type == mgs_cache_dbm
642            || sc->cache_type == mgs_cache_gdbm) {
643        return 0;
644    }
645#if HAVE_APR_MEMCACHE
646    else if (sc->cache_type == mgs_cache_memcache) {
647        return mc_cache_child_init(p, s, sc);
648    }
649#endif
650    return 0;
651}
652
653#include <assert.h>
654
655int mgs_cache_session_init(mgs_handle_t * ctxt) {
656    if (ctxt->sc->cache_type == mgs_cache_dbm
657            || ctxt->sc->cache_type == mgs_cache_gdbm) {
658        gnutls_db_set_retrieve_function(ctxt->session,
659                dbm_cache_fetch_session);
660        gnutls_db_set_remove_function(ctxt->session,
661                dbm_cache_delete);
662        gnutls_db_set_store_function(ctxt->session,
663                dbm_cache_store_session);
664        gnutls_db_set_ptr(ctxt->session, ctxt);
665    }
666#if HAVE_APR_MEMCACHE
667    else if (ctxt->sc->cache_type == mgs_cache_memcache) {
668        gnutls_db_set_retrieve_function(ctxt->session,
669                mc_cache_fetch);
670        gnutls_db_set_remove_function(ctxt->session,
671                mc_cache_delete);
672        gnutls_db_set_store_function(ctxt->session,
673                mc_cache_store);
674        gnutls_db_set_ptr(ctxt->session, ctxt);
675    }
676#endif
677
678    return 0;
679}
Note: See TracBrowser for help on using the repository browser.