1 /*
2 * ProFTPD: mod_tls_redis -- a module which provides shared SSL session
3 * and OCSP response caches using Redis servers
4 * Copyright (c) 2017-2020 TJ Saunders
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19 *
20 * As a special exemption, TJ Saunders and other respective copyright holders
21 * give permission to link this program with OpenSSL, and distribute the
22 * resulting executable, without including the source code for OpenSSL in the
23 * source distribution.
24 *
25 * This is mod_tls_redis, contrib software for proftpd 1.3.x and above.
26 * For more information contact TJ Saunders <tj@castaglia.org>.
27 */
28
29 #include "conf.h"
30 #include "privs.h"
31 #include "mod_tls.h"
32 #include "json.h"
33
34 #define MOD_TLS_REDIS_VERSION "mod_tls_redis/0.1"
35
36 /* Make sure the version of proftpd is as necessary. */
37 #if PROFTPD_VERSION_NUMBER < 0x0001030605
38 # error "ProFTPD 1.3.6rc5 or later required"
39 #endif
40
41 module tls_redis_module;
42
43 /* For communicating with Redis servers for shared data. */
44 static pr_redis_t *sess_redis = NULL;
45
46 /* Assume a maximum SSL session (serialized) length of 10K. Note that this
47 * is different from the SSL_MAX_SSL_SESSION_ID_LENGTH provided by OpenSSL.
48 * There is no limit imposed on the length of the ASN1 description of the
49 * SSL session data.
50 */
51 #ifndef TLS_MAX_SSL_SESSION_SIZE
52 # define TLS_MAX_SSL_SESSION_SIZE 1024 * 10
53 #endif
54
55 struct sesscache_entry {
56 uint32_t expires;
57 unsigned int sess_datalen;
58 unsigned char sess_data[TLS_MAX_SSL_SESSION_SIZE];
59 };
60
61 /* These are the JSON format field names */
62 #define SESS_CACHE_JSON_KEY_EXPIRES "expires"
63 #define SESS_CACHE_JSON_KEY_DATA "data"
64 #define SESS_CACHE_JSON_KEY_DATA_LENGTH "data_len"
65
66 /* The difference between sesscache_entry and sesscache_large_entry is that the
67 * buffers in the latter are dynamically allocated from the heap, not
68 * stored in Redis (given that Redis has limits on how much it can store). The
69 * large_entry struct is used for storing sessions which don't fit into Redis;
70 * this also means that these large entries are NOT shared across processes.
71 */
72 struct sesscache_large_entry {
73 time_t expires;
74 unsigned int sess_id_len;
75 const unsigned char *sess_id;
76 unsigned int sess_datalen;
77 const unsigned char *sess_data;
78 };
79
80 /* These stats are stored in Redis as well, so that the status command can
81 * be run on _any_ proftpd in the cluster.
82 */
83 struct sesscache_key {
84 const char *key;
85 const char *desc;
86 };
87
88 static struct sesscache_key sesscache_keys[] = {
89 { "cache_hits", "Cache lifetime hits" },
90 { "cache_misses", "Cache lifetime misses" },
91 { "cache_stores", "Cache lifetime sessions stored" },
92 { "cache_deletes", "Cache lifetime sessions deleted" },
93 { "cache_errors", "Cache lifetime errors handling sessions in cache" },
94 { "cache_exceeds", "Cache lifetime sessions exceeding max entry size" },
95 { "cache_max_sess_len", "Largest session exceeding max entry size" },
96 { NULL, NULL }
97 };
98
99 /* Indexes into the sesscache_keys array */
100 #define SESSCACHE_KEY_HITS 0
101 #define SESSCACHE_KEY_MISSES 1
102 #define SESSCACHE_KEY_STORES 2
103 #define SESSCACHE_KEY_DELETES 3
104 #define SESSCACHE_KEY_ERRORS 4
105 #define SESSCACHE_KEY_EXCEEDS 5
106 #define SESSCACHE_KEY_MAX_LEN 6
107
108 static tls_sess_cache_t sess_cache;
109 static array_header *sesscache_sess_list = NULL;
110
111 #if defined(PR_USE_OPENSSL_OCSP)
112 static pr_redis_t *ocsp_redis = NULL;
113
114 /* Assume a maximum OCSP response (serialized) length of 4K. */
115 # ifndef TLS_MAX_OCSP_RESPONSE_SIZE
116 # define TLS_MAX_OCSP_RESPONSE_SIZE 1024 * 4
117 # endif
118
119 struct ocspcache_entry {
120 time_t age;
121 unsigned int fingerprint_len;
122 char fingerprint[EVP_MAX_MD_SIZE];
123 unsigned int resp_derlen;
124 unsigned char resp_der[TLS_MAX_OCSP_RESPONSE_SIZE];
125 };
126
127 /* These are the JSON format field names */
128 #define OCSP_CACHE_JSON_KEY_AGE "expires"
129 #define OCSP_CACHE_JSON_KEY_RESPONSE "response"
130 #define OCSP_CACHE_JSON_KEY_RESPONSE_LENGTH "response_len"
131
132 /* The difference between ocspcache_entry and ocspcache_large_entry is that the
133 * buffers in the latter are dynamically allocated from the heap, not
134 * stored in Redis (given that Redis has limits on how much it can store). The
135 * large_entry struct is used for storing responses which don't fit into Redis;
136 * this also means that these large entries are NOT shared across processes.
137 */
138 struct ocspcache_large_entry {
139 time_t age;
140 unsigned int fingerprint_len;
141 char *fingerprint;
142 unsigned int resp_derlen;
143 unsigned char *resp_der;
144 };
145
146 /* These stats are stored in Redis as well, so that the status command can be
147 * run on _any_ proftpd in the cluster.
148 */
149 struct ocspcache_key {
150 const char *key;
151 const char *desc;
152 };
153
154 static struct ocspcache_key ocspcache_keys[] = {
155 { "cache_hits", "Cache lifetime hits" },
156 { "cache_misses", "Cache lifetime misses" },
157 { "cache_stores", "Cache lifetime responses stored" },
158 { "cache_deletes", "Cache lifetime responses deleted" },
159 { "cache_errors", "Cache lifetime errors handling responses in cache" },
160 { "cache_exceeds", "Cache lifetime responses exceeding max entry size" },
161 { "cache_max_resp_len", "Largest response exceeding max entry size" },
162 { NULL, NULL }
163 };
164
165 /* Indexes into the ocspcache_keys array */
166 #define OCSPCACHE_KEY_HITS 0
167 #define OCSPCACHE_KEY_MISSES 1
168 #define OCSPCACHE_KEY_STORES 2
169 #define OCSPCACHE_KEY_DELETES 3
170 #define OCSPCACHE_KEY_ERRORS 4
171 #define OCSPCACHE_KEY_EXCEEDS 5
172 #define OCSPCACHE_KEY_MAX_LEN 6
173
174 static tls_ocsp_cache_t ocsp_cache;
175 static array_header *ocspcache_resp_list = NULL;
176 #endif
177
178 static const char *trace_channel = "tls.redis";
179
180 static int sess_cache_close(tls_sess_cache_t *);
181 #if defined(PR_USE_OPENSSL_OCSP)
182 static int ocsp_cache_close(tls_ocsp_cache_t *);
183 #endif /* PR_USE_OPENSSL_OCSP */
184 static int tls_redis_sess_init(void);
185
redis_get_errors(void)186 static const char *redis_get_errors(void) {
187 unsigned int count = 0;
188 unsigned long error_code;
189 BIO *bio = NULL;
190 char *data = NULL;
191 long datalen;
192 const char *error_data = NULL, *str = "(unknown)";
193 int error_flags = 0;
194
195 /* Use ERR_print_errors() and a memory BIO to build up a string with
196 * all of the error messages from the error queue.
197 */
198
199 error_code = ERR_get_error_line_data(NULL, NULL, &error_data, &error_flags);
200 if (error_code) {
201 bio = BIO_new(BIO_s_mem());
202 }
203
204 while (error_code) {
205 pr_signals_handle();
206
207 if (error_flags & ERR_TXT_STRING) {
208 BIO_printf(bio, "\n (%u) %s [%s]", ++count,
209 ERR_error_string(error_code, NULL), error_data);
210
211 } else {
212 BIO_printf(bio, "\n (%u) %s", ++count,
213 ERR_error_string(error_code, NULL));
214 }
215
216 error_data = NULL;
217 error_flags = 0;
218 error_code = ERR_get_error_line_data(NULL, NULL, &error_data, &error_flags);
219 }
220
221 datalen = BIO_get_mem_data(bio, &data);
222 if (data) {
223 data[datalen] = '\0';
224 str = pstrdup(permanent_pool, data);
225 }
226
227 if (bio != NULL) {
228 BIO_free(bio);
229 }
230
231 return str;
232 }
233
234 /* SSL session cache implementation callbacks.
235 */
236
237 /* Functions for marshalling key/value data to/from Redis. */
238
sess_cache_get_json_key(pool * p,const unsigned char * sess_id,unsigned int sess_id_len,void ** key,size_t * keysz)239 static int sess_cache_get_json_key(pool *p, const unsigned char *sess_id,
240 unsigned int sess_id_len, void **key, size_t *keysz) {
241 pr_json_object_t *json;
242 char *sess_id_hex, *json_text;
243
244 sess_id_hex = pr_str_bin2hex(p, sess_id, sess_id_len, 0);
245 json = pr_json_object_alloc(p);
246 (void) pr_json_object_set_string(p, json, "id", sess_id_hex);
247
248 json_text = pr_json_object_to_text(p, json, "");
249 (void) pr_json_object_free(json);
250
251 /* Include the terminating NUL in the key. */
252 *keysz = strlen(json_text) + 1;
253 *key = pstrndup(p, json_text, *keysz - 1);
254
255 return 0;
256 }
257
sess_cache_get_key(pool * p,const unsigned char * sess_id,unsigned int sess_id_len,void ** key,size_t * keysz)258 static int sess_cache_get_key(pool *p, const unsigned char *sess_id,
259 unsigned int sess_id_len, void **key, size_t *keysz) {
260 int res;
261
262 res = sess_cache_get_json_key(p, sess_id, sess_id_len, key, keysz);
263 if (res < 0) {
264 pr_trace_msg(trace_channel, 3,
265 "error constructing cache JSON lookup key for session ID (%lu bytes)",
266 (unsigned long) keysz);
267 return -1;
268 }
269
270 return 0;
271 }
272
entry_get_json_number(pool * p,pr_json_object_t * json,const char * key,double * val,const char * text)273 static int entry_get_json_number(pool *p, pr_json_object_t *json,
274 const char *key, double *val, const char *text) {
275 if (pr_json_object_get_number(p, json, key, val) < 0) {
276 if (errno == EEXIST) {
277 pr_trace_msg(trace_channel, 3,
278 "ignoring non-number '%s' JSON field in '%s'", key, text);
279
280 } else {
281 tls_log(MOD_TLS_REDIS_VERSION
282 ": missing required '%s' JSON field in '%s'", key, text);
283 }
284
285 (void) pr_json_object_free(json);
286 errno = EINVAL;
287 return -1;
288 }
289
290 return 0;
291 }
292
entry_get_json_string(pool * p,pr_json_object_t * json,const char * key,char ** val,const char * text)293 static int entry_get_json_string(pool *p, pr_json_object_t *json,
294 const char *key, char **val, const char *text) {
295 if (pr_json_object_get_string(p, json, key, val) < 0) {
296 if (errno == EEXIST) {
297 pr_trace_msg(trace_channel, 3,
298 "ignoring non-string '%s' JSON field in '%s'", key, text);
299
300 } else {
301 tls_log(MOD_TLS_REDIS_VERSION
302 ": missing required '%s' JSON field in '%s'", key, text);
303 }
304
305 (void) pr_json_object_free(json);
306 errno = EINVAL;
307 return -1;
308 }
309
310 return 0;
311 }
312
sess_cache_entry_decode_json(pool * p,void * value,size_t valuesz,struct sesscache_entry * se)313 static int sess_cache_entry_decode_json(pool *p, void *value, size_t valuesz,
314 struct sesscache_entry *se) {
315 int res;
316 pr_json_object_t *json;
317 const char *key;
318 char *entry, *text;
319 double number;
320
321 entry = value;
322 if (pr_json_text_validate(p, entry) == FALSE) {
323 tls_log(MOD_TLS_REDIS_VERSION
324 ": unable to decode invalid JSON session cache entry: '%s'", entry);
325 errno = EINVAL;
326 return -1;
327 }
328
329 json = pr_json_object_from_text(p, entry);
330
331 key = SESS_CACHE_JSON_KEY_EXPIRES;
332 res = entry_get_json_number(p, json, key, &number, entry);
333 if (res < 0) {
334 return -1;
335 }
336 se->expires = (uint32_t) number;
337
338 key = SESS_CACHE_JSON_KEY_DATA;
339 res = entry_get_json_string(p, json, key, &text, entry);
340 if (res == 0) {
341 int have_padding = FALSE;
342 char *base64_data;
343 size_t base64_datalen;
344 unsigned char *data;
345
346 base64_data = text;
347 base64_datalen = strlen(base64_data);
348
349 /* Due to Base64's padding, we need to detect if the last block was
350 * padded with zeros; we do this by looking for '=' characters at the
351 * end of the text being decoded. If we see these characters, then we
352 * will "trim" off any trailing zero values in the decoded data, on the
353 * ASSUMPTION that they are the auto-added padding bytes.
354 */
355 if (base64_data[base64_datalen-1] == '=') {
356 have_padding = TRUE;
357 }
358
359 data = se->sess_data;
360 res = EVP_DecodeBlock(data, (unsigned char *) base64_data,
361 (int) base64_datalen);
362 if (res <= 0) {
363 /* Base64-decoding error. */
364 pr_trace_msg(trace_channel, 5,
365 "error base64-decoding session data in '%s', rejecting", entry);
366 errno = EINVAL;
367 return -1;
368 }
369
370 if (have_padding) {
371 /* Assume that only one or two zero bytes of padding were added. */
372 if (data[res-1] == '\0') {
373 res -= 1;
374
375 if (data[res-1] == '\0') {
376 res -= 1;
377 }
378 }
379 }
380
381 } else {
382 return -1;
383 }
384
385 key = SESS_CACHE_JSON_KEY_DATA_LENGTH;
386 res = entry_get_json_number(p, json, key, &number, entry);
387 if (res < 0) {
388 return -1;
389 }
390 se->sess_datalen = (unsigned int) number;
391
392 (void) pr_json_object_free(json);
393 return 0;
394 }
395
sess_cache_redis_entry_get(pool * p,const unsigned char * sess_id,unsigned int sess_id_len,struct sesscache_entry * se)396 static int sess_cache_redis_entry_get(pool *p, const unsigned char *sess_id,
397 unsigned int sess_id_len, struct sesscache_entry *se) {
398 int res;
399 void *key = NULL, *value = NULL;
400 size_t keysz = 0, valuesz = 0;
401
402 res = sess_cache_get_key(p, sess_id, sess_id_len, &key, &keysz);
403 if (res < 0) {
404 pr_trace_msg(trace_channel, 1,
405 "unable to get cache entry: error getting cache key: %s",
406 strerror(errno));
407
408 return -1;
409 }
410
411 value = pr_redis_kget(p, sess_redis, &tls_redis_module, (const char *) key,
412 keysz, &valuesz);
413 if (value == NULL) {
414 pr_trace_msg(trace_channel, 3,
415 "no matching Redis entry found for session ID (%lu bytes)",
416 (unsigned long) keysz);
417 errno = ENOENT;
418 return -1;
419 }
420
421 /* Decode the cached session data. */
422 res = sess_cache_entry_decode_json(p, value, valuesz, se);
423 if (res == 0) {
424 time_t now;
425
426 /* Check for expired cache entries. */
427 time(&now);
428
429 if (se->expires <= now) {
430 pr_trace_msg(trace_channel, 4,
431 "ignoring expired cached session data (expires %lu <= now %lu)",
432 (unsigned long) se->expires, (unsigned long) now);
433 errno = EPERM;
434 return -1;
435 }
436
437 pr_trace_msg(trace_channel, 9, "retrieved JSON session data from cache");
438 }
439
440 return 0;
441 }
442
sess_cache_redis_entry_delete(pool * p,const unsigned char * sess_id,unsigned int sess_id_len)443 static int sess_cache_redis_entry_delete(pool *p, const unsigned char *sess_id,
444 unsigned int sess_id_len) {
445 int res;
446 void *key = NULL;
447 size_t keysz = 0;
448
449 res = sess_cache_get_key(p, sess_id, sess_id_len, &key, &keysz);
450 if (res < 0) {
451 pr_trace_msg(trace_channel, 1,
452 "unable to remove cache entry: error getting cache key: %s",
453 strerror(errno));
454
455 return -1;
456 }
457
458 res = pr_redis_kremove(sess_redis, &tls_redis_module, (const char *) key,
459 keysz);
460 if (res < 0) {
461 int xerrno = errno;
462
463 pr_trace_msg(trace_channel, 2,
464 "unable to remove Redis entry for session ID (%lu bytes): %s",
465 (unsigned long) keysz, strerror(xerrno));
466
467 errno = xerrno;
468 return -1;
469 }
470
471 return 0;
472 }
473
sess_cache_entry_encode_json(pool * p,void ** value,size_t * valuesz,struct sesscache_entry * se)474 static int sess_cache_entry_encode_json(pool *p, void **value, size_t *valuesz,
475 struct sesscache_entry *se) {
476 pr_json_object_t *json;
477 pool *tmp_pool;
478 char *base64_data = NULL, *json_text;
479
480 json = pr_json_object_alloc(p);
481 (void) pr_json_object_set_number(p, json, SESS_CACHE_JSON_KEY_EXPIRES,
482 (double) se->expires);
483
484 /* Base64-encode the session data. Note that EVP_EncodeBlock does
485 * NUL-terminate the encoded data.
486 */
487 tmp_pool = make_sub_pool(p);
488 base64_data = pcalloc(tmp_pool, se->sess_datalen * 2);
489
490 EVP_EncodeBlock((unsigned char *) base64_data, se->sess_data,
491 (int) se->sess_datalen);
492 (void) pr_json_object_set_string(p, json, SESS_CACHE_JSON_KEY_DATA,
493 base64_data);
494 (void) pr_json_object_set_number(p, json, SESS_CACHE_JSON_KEY_DATA_LENGTH,
495 (double) se->sess_datalen);
496 destroy_pool(tmp_pool);
497
498 json_text = pr_json_object_to_text(p, json, "");
499 (void) pr_json_object_free(json);
500
501 /* Safety check */
502 if (pr_json_text_validate(p, json_text) == FALSE) {
503 pr_trace_msg(trace_channel, 1, "invalid JSON emitted: '%s'", json_text);
504 errno = EINVAL;
505 return -1;
506 }
507
508 /* Include the terminating NUL in the value. */
509 *valuesz = strlen(json_text) + 1;
510 *value = pstrndup(p, json_text, *valuesz - 1);
511
512 return 0;
513 }
514
sess_cache_redis_entry_set(pool * p,const unsigned char * sess_id,unsigned int sess_id_len,struct sesscache_entry * se)515 static int sess_cache_redis_entry_set(pool *p, const unsigned char *sess_id,
516 unsigned int sess_id_len, struct sesscache_entry *se) {
517 int res, xerrno = 0;
518 void *key = NULL, *value = NULL;
519 size_t keysz = 0, valuesz = 0;
520
521 /* Encode the SSL session data. */
522 res = sess_cache_entry_encode_json(p, &value, &valuesz, se);
523 if (res < 0) {
524 xerrno = errno;
525
526 pr_trace_msg(trace_channel, 4, "error JSON encoding session data: %s",
527 strerror(xerrno));
528
529 errno = xerrno;
530 return -1;
531 }
532
533 res = sess_cache_get_key(p, sess_id, sess_id_len, &key, &keysz);
534 xerrno = errno;
535 if (res < 0) {
536 pr_trace_msg(trace_channel, 1,
537 "unable to set cache entry: error getting cache key: %s",
538 strerror(xerrno));
539
540 errno = xerrno;
541 return -1;
542 }
543
544 res = pr_redis_kset(sess_redis, &tls_redis_module, (const char *) key,
545 keysz, value, valuesz, se->expires);
546 xerrno = errno;
547
548 if (res < 0) {
549 pr_trace_msg(trace_channel, 2,
550 "unable to add Redis entry for session ID (%lu bytes): %s",
551 (unsigned long) keysz, strerror(xerrno));
552
553 errno = xerrno;
554 return -1;
555 }
556
557 pr_trace_msg(trace_channel, 9, "stored JSON session data in cache");
558 return 0;
559 }
560
sess_cache_open(tls_sess_cache_t * cache,char * info,long timeout)561 static int sess_cache_open(tls_sess_cache_t *cache, char *info, long timeout) {
562 config_rec *c;
563
564 cache->cache_pool = make_sub_pool(permanent_pool);
565 pr_pool_tag(cache->cache_pool, MOD_TLS_REDIS_VERSION);
566
567 pr_trace_msg(trace_channel, 9, "opening Redis cache %p (info '%s')",
568 cache, info ? info : "(none)");
569
570 /* This is a little messy, but necessary. The mod_redis module does not set
571 * the Redis server until a connection arrives. But mod_tls opens its
572 * session cache prior to that, when the server is starting up. Thus we need
573 * to set the configured Redis server ourselves.
574 */
575 c = find_config(main_server->conf, CONF_PARAM, "RedisEngine", FALSE);
576 if (c != NULL) {
577 int engine;
578
579 engine = *((int *) c->argv[0]);
580 if (engine == FALSE) {
581 pr_trace_msg(trace_channel, 2, "%s",
582 "Redis support disabled (see RedisEngine directive)");
583 errno = EPERM;
584 return -1;
585 }
586 }
587
588 sess_redis = pr_redis_conn_new(cache->cache_pool, &tls_redis_module, 0);
589 if (sess_redis == NULL) {
590 pr_trace_msg(trace_channel, 2,
591 "error connecting to Redis: %s", strerror(errno));
592 errno = EPERM;
593 return -1;
594 }
595
596 /* Configure a namespace prefix for our Redis keys. */
597 if (pr_redis_conn_set_namespace(sess_redis, &tls_redis_module,
598 "mod_tls_redis.sessions.", 23) < 0) {
599 pr_trace_msg(trace_channel, 2,
600 "error setting Redis namespace prefix: %s", strerror(errno));
601 }
602
603 cache->cache_timeout = timeout;
604 return 0;
605 }
606
sess_cache_close(tls_sess_cache_t * cache)607 static int sess_cache_close(tls_sess_cache_t *cache) {
608 pr_trace_msg(trace_channel, 9, "closing Redis session cache %p", cache);
609
610 if (cache != NULL &&
611 cache->cache_pool != NULL) {
612
613 /* We do NOT destroy the cache_pool here or close the Redis connection;
614 * both were created at daemon startup, and should live as long as
615 * the daemon lives.
616 */
617
618 if (sesscache_sess_list != NULL) {
619 register unsigned int i;
620 struct sesscache_large_entry *entries;
621
622 entries = sesscache_sess_list->elts;
623 for (i = 0; i < sesscache_sess_list->nelts; i++) {
624 struct sesscache_large_entry *entry;
625
626 entry = &(entries[i]);
627 if (entry->expires > 0) {
628 pr_memscrub((void *) entry->sess_data, entry->sess_datalen);
629 }
630 }
631
632 clear_array(sesscache_sess_list);
633 }
634 }
635
636 return 0;
637 }
638
sess_cache_add_large_sess(tls_sess_cache_t * cache,const unsigned char * sess_id,unsigned int sess_id_len,time_t expires,SSL_SESSION * sess,int sess_len)639 static int sess_cache_add_large_sess(tls_sess_cache_t *cache,
640 const unsigned char *sess_id, unsigned int sess_id_len, time_t expires,
641 SSL_SESSION *sess, int sess_len) {
642 struct sesscache_large_entry *entry = NULL;
643
644 if (sess_len > TLS_MAX_SSL_SESSION_SIZE) {
645 int res;
646 const char *exceeds_key = sesscache_keys[SESSCACHE_KEY_EXCEEDS].key,
647 *max_len_key = sesscache_keys[SESSCACHE_KEY_MAX_LEN].key;
648 void *value = NULL;
649 size_t valuesz = 0;
650 pool *tmp_pool;
651
652 res = pr_redis_incr(sess_redis, &tls_redis_module, exceeds_key, 1, NULL);
653 if (res < 0) {
654 pr_trace_msg(trace_channel, 2,
655 "error incrementing '%s' value: %s", exceeds_key, strerror(errno));
656 }
657
658 /* XXX Yes, this is subject to race conditions; other proftpd servers
659 * might also be modifying this value in Redis. Oh well.
660 */
661
662 tmp_pool = make_sub_pool(cache->cache_pool);
663 value = pr_redis_get(tmp_pool, sess_redis, &tls_redis_module, max_len_key,
664 &valuesz);
665 if (value != NULL) {
666 uint64_t max_len;
667
668 memcpy(&max_len, value, valuesz);
669 if ((uint64_t) sess_len > max_len) {
670 if (pr_redis_set(sess_redis, &tls_redis_module, max_len_key, &max_len,
671 sizeof(max_len), 0) < 0) {
672 pr_trace_msg(trace_channel, 2,
673 "error setting '%s' value: %s", max_len_key, strerror(errno));
674 }
675 }
676
677 } else {
678 pr_trace_msg(trace_channel, 2,
679 "error getting '%s' value: %s", max_len_key, strerror(errno));
680 }
681
682 destroy_pool(tmp_pool);
683 }
684
685 if (sesscache_sess_list != NULL) {
686 register unsigned int i;
687 struct sesscache_large_entry *entries;
688 time_t now;
689 int ok = FALSE;
690
691 /* Look for any expired sessions in the list to overwrite/reuse. */
692 entries = sesscache_sess_list->elts;
693 time(&now);
694 for (i = 0; i < sesscache_sess_list->nelts; i++) {
695 entry = &(entries[i]);
696
697 if (entry->expires <= now) {
698 /* This entry has expired; clear and reuse its slot. */
699 entry->expires = 0;
700 pr_memscrub((void *) entry->sess_data, entry->sess_datalen);
701
702 ok = TRUE;
703 break;
704 }
705 }
706
707 if (!ok) {
708 /* We didn't find an open slot in the list. Need to add one. */
709 entry = push_array(sesscache_sess_list);
710 }
711
712 } else {
713 sesscache_sess_list = make_array(cache->cache_pool, 1,
714 sizeof(struct sesscache_large_entry));
715 entry = push_array(sesscache_sess_list);
716 }
717
718 entry->expires = expires;
719 entry->sess_id_len = sess_id_len;
720 entry->sess_id = palloc(cache->cache_pool, sess_id_len);
721 memcpy((unsigned char *) entry->sess_id, sess_id, sess_id_len);
722 entry->sess_datalen = sess_len;
723 entry->sess_data = palloc(cache->cache_pool, sess_len);
724 i2d_SSL_SESSION(sess, (unsigned char **) &(entry->sess_data));
725
726 return 0;
727 }
728
sess_cache_add(tls_sess_cache_t * cache,const unsigned char * sess_id,unsigned int sess_id_len,time_t expires,SSL_SESSION * sess)729 static int sess_cache_add(tls_sess_cache_t *cache, const unsigned char *sess_id,
730 unsigned int sess_id_len, time_t expires, SSL_SESSION *sess) {
731 struct sesscache_entry entry;
732 int sess_len;
733 unsigned char *ptr;
734 time_t now;
735
736 time(&now);
737 pr_trace_msg(trace_channel, 9,
738 "adding session to Redis cache %p (expires = %lu, now = %lu)", cache,
739 (unsigned long) expires, (unsigned long) now);
740
741 /* First we need to find out how much space is needed for the serialized
742 * session data. There is no known maximum size for SSL session data;
743 * this module is currently designed to allow only up to a certain size.
744 */
745 sess_len = i2d_SSL_SESSION(sess, NULL);
746 if (sess_len > TLS_MAX_SSL_SESSION_SIZE) {
747 pr_trace_msg(trace_channel, 2,
748 "length of serialized SSL session data (%d) exceeds maximum size (%u), "
749 "unable to add to shared Redis, adding to list", sess_len,
750 TLS_MAX_SSL_SESSION_SIZE);
751
752 /* Instead of rejecting the add here, we add the session to a "large
753 * session" list. Thus the large session would still be cached per process
754 * and will not be lost.
755 */
756
757 return sess_cache_add_large_sess(cache, sess_id, sess_id_len, expires,
758 sess, sess_len);
759 }
760
761 entry.expires = expires;
762 entry.sess_datalen = sess_len;
763 ptr = entry.sess_data;
764 i2d_SSL_SESSION(sess, &ptr);
765
766 if (sess_cache_redis_entry_set(cache->cache_pool, sess_id, sess_id_len,
767 &entry) < 0) {
768 pr_trace_msg(trace_channel, 2,
769 "error adding session to Redis: %s", strerror(errno));
770
771 /* Add this session to the "large session" list instead as a fallback. */
772 return sess_cache_add_large_sess(cache, sess_id, sess_id_len, expires,
773 sess, sess_len);
774
775 } else {
776 const char *key = sesscache_keys[SESSCACHE_KEY_STORES].key;
777
778 if (pr_redis_incr(sess_redis, &tls_redis_module, key, 1, NULL) < 0) {
779 pr_trace_msg(trace_channel, 2,
780 "error incrementing '%s' value: %s", key, strerror(errno));
781 }
782 }
783
784 return 0;
785 }
786
sess_cache_get(tls_sess_cache_t * cache,const unsigned char * sess_id,unsigned int sess_id_len)787 static SSL_SESSION *sess_cache_get(tls_sess_cache_t *cache,
788 const unsigned char *sess_id, unsigned int sess_id_len) {
789 struct sesscache_entry entry;
790 time_t now;
791 SSL_SESSION *sess = NULL;
792
793 pr_trace_msg(trace_channel, 9, "getting session from Redis cache %p", cache);
794
795 /* Look for the requested session in the "large session" list first. */
796 if (sesscache_sess_list != NULL) {
797 register unsigned int i;
798 struct sesscache_large_entry *entries;
799
800 entries = sesscache_sess_list->elts;
801 for (i = 0; i < sesscache_sess_list->nelts; i++) {
802 struct sesscache_large_entry *large_entry;
803
804 large_entry = &(entries[i]);
805 if (large_entry->expires > 0 &&
806 large_entry->sess_id_len == sess_id_len &&
807 memcmp(large_entry->sess_id, sess_id,
808 large_entry->sess_id_len) == 0) {
809
810 now = time(NULL);
811 if (large_entry->expires > now) {
812 TLS_D2I_SSL_SESSION_CONST unsigned char *ptr;
813
814 ptr = large_entry->sess_data;
815 sess = d2i_SSL_SESSION(NULL, &ptr, large_entry->sess_datalen);
816 if (sess == NULL) {
817 pr_trace_msg(trace_channel, 2,
818 "error retrieving session from cache: %s", redis_get_errors());
819
820 } else {
821 break;
822 }
823 }
824 }
825 }
826 }
827
828 if (sess != NULL) {
829 return sess;
830 }
831
832 if (sess_cache_redis_entry_get(cache->cache_pool, sess_id, sess_id_len,
833 &entry) < 0) {
834 return NULL;
835 }
836
837 now = time(NULL);
838 if (entry.expires > now) {
839 TLS_D2I_SSL_SESSION_CONST unsigned char *ptr;
840
841 ptr = entry.sess_data;
842 sess = d2i_SSL_SESSION(NULL, &ptr, entry.sess_datalen);
843 if (sess != NULL) {
844 const char *key = sesscache_keys[SESSCACHE_KEY_HITS].key;
845
846 if (pr_redis_incr(sess_redis, &tls_redis_module, key, 1, NULL) < 0) {
847 pr_trace_msg(trace_channel, 2,
848 "error incrementing '%s' value: %s", key, strerror(errno));
849 }
850
851 } else {
852 const char *key = sesscache_keys[SESSCACHE_KEY_ERRORS].key;
853
854 pr_trace_msg(trace_channel, 2,
855 "error retrieving session from cache: %s", redis_get_errors());
856
857 if (pr_redis_incr(sess_redis, &tls_redis_module, key, 1,
858 NULL) < 0) {
859 pr_trace_msg(trace_channel, 2,
860 "error incrementing '%s' value: %s", key, strerror(errno));
861 }
862 }
863 }
864
865 if (sess == NULL) {
866 const char *key = sesscache_keys[SESSCACHE_KEY_MISSES].key;
867
868 if (pr_redis_incr(sess_redis, &tls_redis_module, key, 1, NULL) < 0) {
869 pr_trace_msg(trace_channel, 2,
870 "error incrementing '%s' value: %s", key, strerror(errno));
871 }
872
873 errno = ENOENT;
874 }
875
876 return sess;
877 }
878
sess_cache_delete(tls_sess_cache_t * cache,const unsigned char * sess_id,unsigned int sess_id_len)879 static int sess_cache_delete(tls_sess_cache_t *cache,
880 const unsigned char *sess_id, unsigned int sess_id_len) {
881 const char *key = sesscache_keys[SESSCACHE_KEY_DELETES].key;
882 int res;
883
884 pr_trace_msg(trace_channel, 9, "removing session from Redis cache %p", cache);
885
886 /* Look for the requested session in the "large session" list first. */
887 if (sesscache_sess_list != NULL) {
888 register unsigned int i;
889 struct sesscache_large_entry *entries;
890
891 entries = sesscache_sess_list->elts;
892 for (i = 0; i < sesscache_sess_list->nelts; i++) {
893 struct sesscache_large_entry *entry;
894
895 entry = &(entries[i]);
896 if (entry->sess_id_len == sess_id_len &&
897 memcmp(entry->sess_id, sess_id, entry->sess_id_len) == 0) {
898
899 pr_memscrub((void *) entry->sess_data, entry->sess_datalen);
900 entry->expires = 0;
901 return 0;
902 }
903 }
904 }
905
906 res = sess_cache_redis_entry_delete(cache->cache_pool, sess_id, sess_id_len);
907 if (res < 0) {
908 return -1;
909 }
910
911 /* Don't forget to update the stats. */
912
913 if (pr_redis_incr(sess_redis, &tls_redis_module, key, 1, NULL) < 0) {
914 pr_trace_msg(trace_channel, 2,
915 "error incrementing '%s' value: %s", key, strerror(errno));
916 }
917
918 return res;
919 }
920
sess_cache_clear(tls_sess_cache_t * cache)921 static int sess_cache_clear(tls_sess_cache_t *cache) {
922 register unsigned int i;
923 int res = 0;
924
925 if (sess_redis == NULL) {
926 pr_trace_msg(trace_channel, 9, "missing required Redis connection");
927 errno = EINVAL;
928 return -1;
929 }
930
931 pr_trace_msg(trace_channel, 9, "clearing Redis session cache %p", cache);
932
933 if (sesscache_sess_list != NULL) {
934 struct sesscache_large_entry *entries;
935
936 entries = sesscache_sess_list->elts;
937 for (i = 0; i < sesscache_sess_list->nelts; i++) {
938 struct sesscache_large_entry *entry;
939
940 entry = &(entries[i]);
941 entry->expires = 0;
942 pr_memscrub((void *) entry->sess_data, entry->sess_datalen);
943 }
944 }
945
946 /* XXX iterate through keys, kremoving any "mod_tls_redis" prefixed keys */
947
948 return res;
949 }
950
sess_cache_remove(tls_sess_cache_t * cache)951 static int sess_cache_remove(tls_sess_cache_t *cache) {
952 int res;
953
954 pr_trace_msg(trace_channel, 9, "removing Redis session cache %p", cache);
955
956 res = sess_cache_clear(cache);
957 /* XXX close Redis conn */
958
959 return res;
960 }
961
sess_cache_status(tls_sess_cache_t * cache,void (* statusf)(void *,const char *,...),void * arg,int flags)962 static int sess_cache_status(tls_sess_cache_t *cache,
963 void (*statusf)(void *, const char *, ...), void *arg, int flags) {
964 register unsigned int i;
965 pool *tmp_pool;
966
967 pr_trace_msg(trace_channel, 9, "checking Redis session cache %p", cache);
968
969 tmp_pool = make_sub_pool(permanent_pool);
970
971 statusf(arg, "%s", "Redis SSL session cache provided by "
972 MOD_TLS_REDIS_VERSION);
973 statusf(arg, "%s", "");
974 statusf(arg, "Redis server: ");
975
976 for (i = 0; sesscache_keys[i].key != NULL; i++) {
977 const char *key, *desc;
978 void *value = NULL;
979 size_t valuesz = 0;
980
981 key = sesscache_keys[i].key;
982 desc = sesscache_keys[i].desc;
983
984 value = pr_redis_get(tmp_pool, sess_redis, &tls_redis_module, key,
985 &valuesz);
986 if (value != NULL) {
987 uint64_t num = 0;
988 memcpy(&num, value, valuesz);
989 statusf(arg, "%s: %lu", desc, (unsigned long) num);
990 }
991 }
992
993 /* XXX run stats on Redis servers? */
994
995 #if 0
996 if (flags & TLS_SESS_CACHE_STATUS_FL_SHOW_SESSIONS) {
997 statusf(arg, "%s", "");
998 statusf(arg, "%s", "Cached sessions:");
999
1000 /* XXX Get keys, looking for our namespace prefix, dump each one */
1001
1002 /* We _could_ use SSL_SESSION_print(), which is what the sess_id
1003 * command-line tool does. The problem is that SSL_SESSION_print() shows
1004 * too much (particularly, it shows the master secret). And
1005 * SSL_SESSION_print() does not support a flags argument to use for
1006 * specifying which bits of the session we want to print.
1007 *
1008 * Instead, we get to do the more dangerous (compatibility-wise) approach
1009 * of rolling our own printing function.
1010 */
1011
1012 for (i = 0; i < 0; i++) {
1013 struct sesscache_entry *entry;
1014
1015 pr_signals_handle();
1016
1017 /* XXX Get entries */
1018 if (entry->expires > 0) {
1019 SSL_SESSION *sess;
1020 TLS_D2I_SSL_SESSION_CONST unsigned char *ptr;
1021 time_t ts;
1022
1023 ptr = entry->sess_data;
1024 sess = d2i_SSL_SESSION(NULL, &ptr, entry->sess_datalen);
1025 if (sess == NULL) {
1026 pr_log_pri(PR_LOG_NOTICE, MOD_TLS_REDIS_VERSION
1027 ": error retrieving session from cache: %s", redis_get_errors());
1028 continue;
1029 }
1030
1031 statusf(arg, "%s", " -----BEGIN SSL SESSION PARAMETERS-----");
1032
1033 /* XXX Directly accessing these fields cannot be a Good Thing. */
1034 if (sess->session_id_length > 0) {
1035 char *sess_id_str;
1036
1037 sess_id_str = pr_str2hex(tmp_pool, sess->session_id,
1038 sess->session_id_length, PR_STR_FL_HEX_USE_UC);
1039
1040 statusf(arg, " Session ID: %s", sess_id_str);
1041 }
1042
1043 if (sess->sid_ctx_length > 0) {
1044 char *sid_ctx_str;
1045
1046 sid_ctx_str = pr_str2hex(tmp_pool, sess->sid_ctx,
1047 sess->sid_ctx_length, PR_STR_FL_HEX_USE_UC);
1048
1049 statusf(arg, " Session ID Context: %s", sid_ctx_str);
1050 }
1051
1052 switch (sess->ssl_version) {
1053 case SSL3_VERSION:
1054 statusf(arg, " Protocol: %s", "SSLv3");
1055 break;
1056
1057 case TLS1_VERSION:
1058 statusf(arg, " Protocol: %s", "TLSv1");
1059 break;
1060
1061 #if defined(TLS1_1_VERSION)
1062 case TLS1_1_VERSION:
1063 statusf(arg, " Protocol: %s", "TLSv1.1");
1064 break;
1065 #endif /* TLS1_1_VERSION */
1066
1067 #if defined(TLS1_2_VERSION)
1068 case TLS1_2_VERSION:
1069 statusf(arg, " Protocol: %s", "TLSv1.2");
1070 break;
1071 #endif /* TLS1_2_VERSION */
1072
1073 #if defined(TLS1_3_VERSION)
1074 case TLS1_3_VERSION:
1075 statusf(arg, " Protocol: %s", "TLSv1.3");
1076 break;
1077 #endif /* TLS1_3_VERSION */
1078
1079 default:
1080 statusf(arg, " Protocol: %s", "unknown");
1081 }
1082
1083 ts = SSL_SESSION_get_time(sess);
1084 statusf(arg, " Started: %s", pr_strtime3(tmp_pool, ts, FALSE));
1085 ts = entry->expires;
1086 statusf(arg, " Expires: %s (%u secs)",
1087 pr_strtime3(tmp_pool, ts, FALSE), SSL_SESSION_get_timeout(sess));
1088
1089 SSL_SESSION_free(sess);
1090 statusf(arg, "%s", " -----END SSL SESSION PARAMETERS-----");
1091 statusf(arg, "%s", "");
1092 }
1093 }
1094 }
1095 #endif
1096
1097 destroy_pool(tmp_pool);
1098 return 0;
1099 }
1100
1101 #if defined(PR_USE_OPENSSL_OCSP)
1102 /* OCSP response cache implementation callbacks.
1103 */
1104
1105 /* Functions for marshalling key/value data to/from Redis. */
1106
ocsp_cache_get_json_key(pool * p,const char * fingerprint,void ** key,size_t * keysz)1107 static int ocsp_cache_get_json_key(pool *p, const char *fingerprint,
1108 void **key, size_t *keysz) {
1109 pr_json_object_t *json;
1110 char *json_text;
1111
1112 json = pr_json_object_alloc(p);
1113 (void) pr_json_object_set_string(p, json, "fingerprint", fingerprint);
1114
1115 json_text = pr_json_object_to_text(p, json, "");
1116 (void) pr_json_object_free(json);
1117
1118 /* Include the terminating NUL in the key. */
1119 *keysz = strlen(json_text) + 1;
1120 *key = pstrndup(p, json_text, *keysz - 1);
1121
1122 return 0;
1123 }
1124
ocsp_cache_get_key(pool * p,const char * fingerprint,void ** key,size_t * keysz)1125 static int ocsp_cache_get_key(pool *p, const char *fingerprint, void **key,
1126 size_t *keysz) {
1127 int res;
1128
1129 res = ocsp_cache_get_json_key(p, fingerprint, key, keysz);
1130 if (res < 0) {
1131 pr_trace_msg(trace_channel, 3,
1132 "error constructing ocsp cache JSON lookup key for fingerprint '%s'",
1133 fingerprint);
1134 return -1;
1135 }
1136
1137 return 0;
1138 }
1139
ocsp_cache_entry_decode_json(pool * p,void * value,size_t valuesz,struct ocspcache_entry * oe)1140 static int ocsp_cache_entry_decode_json(pool *p, void *value, size_t valuesz,
1141 struct ocspcache_entry *oe) {
1142 int res;
1143 pr_json_object_t *json;
1144 const char *key;
1145 char *entry, *text;
1146 double number;
1147
1148 entry = value;
1149 if (pr_json_text_validate(p, entry) == FALSE) {
1150 tls_log(MOD_TLS_REDIS_VERSION
1151 ": unable to decode invalid JSON ocsp cache entry: '%s'", entry);
1152 errno = EINVAL;
1153 return -1;
1154 }
1155
1156 json = pr_json_object_from_text(p, entry);
1157
1158 key = OCSP_CACHE_JSON_KEY_AGE;
1159 res = entry_get_json_number(p, json, key, &number, entry);
1160 if (res < 0) {
1161 return -1;
1162 }
1163 oe->age = (uint32_t) number;
1164
1165 key = OCSP_CACHE_JSON_KEY_RESPONSE;
1166 res = entry_get_json_string(p, json, key, &text, entry);
1167 if (res == 0) {
1168 int have_padding = FALSE;
1169 char *base64_data;
1170 size_t base64_datalen;
1171 unsigned char *data;
1172
1173 base64_data = text;
1174 base64_datalen = strlen(base64_data);
1175
1176 /* Due to Base64's padding, we need to detect if the last block was
1177 * padded with zeros; we do this by looking for '=' characters at the
1178 * end of the text being decoded. If we see these characters, then we
1179 * will "trim" off any trailing zero values in the decoded data, on the
1180 * ASSUMPTION that they are the auto-added padding bytes.
1181 */
1182 if (base64_data[base64_datalen-1] == '=') {
1183 have_padding = TRUE;
1184 }
1185
1186 data = oe->resp_der;
1187 res = EVP_DecodeBlock(data, (unsigned char *) base64_data,
1188 (int) base64_datalen);
1189 if (res <= 0) {
1190 /* Base64-decoding error. */
1191 pr_trace_msg(trace_channel, 5,
1192 "error base64-decoding OCSP data in '%s', rejecting", entry);
1193 (void) pr_json_object_free(json);
1194 errno = EINVAL;
1195 return -1;
1196 }
1197
1198 if (have_padding) {
1199 /* Assume that only one or two zero bytes of padding were added. */
1200 if (data[res-1] == '\0') {
1201 res -= 1;
1202
1203 if (data[res-1] == '\0') {
1204 res -= 1;
1205 }
1206 }
1207 }
1208
1209 } else {
1210 return -1;
1211 }
1212
1213 key = OCSP_CACHE_JSON_KEY_RESPONSE_LENGTH;
1214 res = entry_get_json_number(p, json, key, &number, entry);
1215 if (res < 0) {
1216 return -1;
1217 }
1218 oe->resp_derlen = (unsigned int) number;
1219
1220 pr_json_object_free(json);
1221 return 0;
1222 }
1223
ocsp_cache_redis_entry_get(pool * p,const char * fingerprint,struct ocspcache_entry * oe)1224 static int ocsp_cache_redis_entry_get(pool *p, const char *fingerprint,
1225 struct ocspcache_entry *oe) {
1226 int res;
1227 void *key = NULL, *value = NULL;
1228 size_t keysz = 0, valuesz = 0;
1229
1230 res = ocsp_cache_get_key(p, fingerprint, &key, &keysz);
1231 if (res < 0) {
1232 pr_trace_msg(trace_channel, 1,
1233 "unable to get ocsp cache entry: error getting cache key: %s",
1234 strerror(errno));
1235
1236 return -1;
1237 }
1238
1239 value = pr_redis_kget(p, ocsp_redis, &tls_redis_module, (const char *) key,
1240 keysz, &valuesz);
1241 if (value == NULL) {
1242 pr_trace_msg(trace_channel, 3,
1243 "no matching Redis entry found for fingerprint '%s'", fingerprint);
1244 errno = ENOENT;
1245 return -1;
1246 }
1247
1248 /* Decode the cached response data. */
1249 res = ocsp_cache_entry_decode_json(p, value, valuesz, oe);
1250 if (res == 0) {
1251 pr_trace_msg(trace_channel, 9, "retrieved JSON response data from cache");
1252 }
1253
1254 return 0;
1255 }
1256
ocsp_cache_redis_entry_delete(pool * p,const char * fingerprint)1257 static int ocsp_cache_redis_entry_delete(pool *p, const char *fingerprint) {
1258 int res;
1259 void *key = NULL;
1260 size_t keysz = 0;
1261
1262 res = ocsp_cache_get_key(p, fingerprint, &key, &keysz);
1263 if (res < 0) {
1264 pr_trace_msg(trace_channel, 1,
1265 "unable to remove ocsp cache entry: error getting cache key: %s",
1266 strerror(errno));
1267
1268 return -1;
1269 }
1270
1271 res = pr_redis_kremove(ocsp_redis, &tls_redis_module, (const char *) key,
1272 keysz);
1273 if (res < 0) {
1274 int xerrno = errno;
1275
1276 pr_trace_msg(trace_channel, 2,
1277 "unable to remove Redis entry for fingerpring '%s': %s", fingerprint,
1278 strerror(xerrno));
1279
1280 errno = xerrno;
1281 return -1;
1282 }
1283
1284 return 0;
1285 }
1286
ocsp_cache_entry_encode_json(pool * p,void ** value,size_t * valuesz,struct ocspcache_entry * oe)1287 static int ocsp_cache_entry_encode_json(pool *p, void **value, size_t *valuesz,
1288 struct ocspcache_entry *oe) {
1289 pr_json_object_t *json;
1290 pool *tmp_pool;
1291 char *base64_data = NULL, *json_text;
1292
1293 json = pr_json_object_alloc(p);
1294 (void) pr_json_object_set_number(p, json, OCSP_CACHE_JSON_KEY_AGE,
1295 (double) oe->age);
1296
1297 /* Base64-encode the response data. Note that EVP_EncodeBlock does
1298 * NUL-terminate the encoded data.
1299 */
1300 tmp_pool = make_sub_pool(p);
1301 base64_data = pcalloc(tmp_pool, (oe->resp_derlen * 2) + 1);
1302
1303 EVP_EncodeBlock((unsigned char *) base64_data, oe->resp_der,
1304 (int) oe->resp_derlen);
1305 (void) pr_json_object_set_string(p, json, OCSP_CACHE_JSON_KEY_RESPONSE,
1306 base64_data);
1307 (void) pr_json_object_set_number(p, json, OCSP_CACHE_JSON_KEY_RESPONSE_LENGTH,
1308 (double) oe->resp_derlen);
1309 destroy_pool(tmp_pool);
1310
1311 json_text = pr_json_object_to_text(p, json, "");
1312 (void) pr_json_object_free(json);
1313
1314 /* Safety check */
1315 if (pr_json_text_validate(p, json_text) == FALSE) {
1316 pr_trace_msg(trace_channel, 1, "invalid JSON emitted: '%s'", json_text);
1317 errno = EINVAL;
1318 return -1;
1319 }
1320
1321 /* Include the terminating NUL in the value. */
1322 *valuesz = strlen(json_text) + 1;
1323 *value = pstrndup(p, json_text, *valuesz - 1);
1324
1325 return 0;
1326 }
1327
ocsp_cache_redis_entry_set(pool * p,const char * fingerprint,struct ocspcache_entry * oe)1328 static int ocsp_cache_redis_entry_set(pool *p, const char *fingerprint,
1329 struct ocspcache_entry *oe) {
1330 int res, xerrno = 0;
1331 void *key = NULL, *value = NULL;
1332 size_t keysz = 0, valuesz = 0;
1333
1334 /* Encode the OCSP response data. */
1335 res = ocsp_cache_entry_encode_json(p, &value, &valuesz, oe);
1336 if (res < 0) {
1337 xerrno = errno;
1338
1339 pr_trace_msg(trace_channel, 4, "error JSON encoding OCSP response data: %s",
1340 strerror(xerrno));
1341
1342 errno = xerrno;
1343 return -1;
1344 }
1345
1346 res = ocsp_cache_get_key(p, fingerprint, &key, &keysz);
1347 xerrno = errno;
1348 if (res < 0) {
1349 pr_trace_msg(trace_channel, 1,
1350 "unable to set ocsp cache entry: error getting cache key: %s",
1351 strerror(xerrno));
1352
1353 errno = xerrno;
1354 return -1;
1355 }
1356
1357 res = pr_redis_kset(ocsp_redis, &tls_redis_module, (const char *) key, keysz,
1358 value, valuesz, 0);
1359 xerrno = errno;
1360
1361 if (res < 0) {
1362 pr_trace_msg(trace_channel, 2,
1363 "unable to add Redis entry for fingerprint '%s': %s", fingerprint,
1364 strerror(xerrno));
1365
1366 errno = xerrno;
1367 return -1;
1368 }
1369
1370 pr_trace_msg(trace_channel, 9, "stored OCSP JSON response data in cache");
1371 return 0;
1372 }
1373
ocsp_cache_open(tls_ocsp_cache_t * cache,char * info)1374 static int ocsp_cache_open(tls_ocsp_cache_t *cache, char *info) {
1375 config_rec *c;
1376
1377 pr_trace_msg(trace_channel, 9, "opening Redis cache %p (info '%s')",
1378 cache, info ? info : "(none)");
1379
1380 cache->cache_pool = make_sub_pool(permanent_pool);
1381 pr_pool_tag(cache->cache_pool, MOD_TLS_REDIS_VERSION);
1382
1383 /* This is a little messy, but necessary. The mod_redis module does not set
1384 * the configured Redis server until a connection arrives. But mod_tls
1385 * opens its session cache prior to that, when the server is starting up.
1386 * Thus we need to set the configured Redis server ourselves.
1387 */
1388 c = find_config(main_server->conf, CONF_PARAM, "RedisEngine", FALSE);
1389 if (c != NULL) {
1390 int engine;
1391
1392 engine = *((int *) c->argv[0]);
1393 if (engine == FALSE) {
1394 pr_trace_msg(trace_channel, 2, "%s",
1395 "Redis support disabled (see RedisEngine directive)");
1396 errno = EPERM;
1397 return -1;
1398 }
1399 }
1400
1401 ocsp_redis = pr_redis_conn_new(cache->cache_pool, &tls_redis_module, 0);
1402 if (ocsp_redis == NULL) {
1403 pr_trace_msg(trace_channel, 2,
1404 "error connecting to Redis: %s", strerror(errno));
1405 errno = EPERM;
1406 return -1;
1407 }
1408
1409 /* Configure a namespace prefix for our Redis keys. */
1410 if (pr_redis_conn_set_namespace(ocsp_redis, &tls_redis_module,
1411 "mod_tls_redis.ocsp.", 19) < 0) {
1412 pr_trace_msg(trace_channel, 2,
1413 "error setting Redis namespace prefix: %s", strerror(errno));
1414 }
1415
1416 return 0;
1417 }
1418
ocsp_cache_close(tls_ocsp_cache_t * cache)1419 static int ocsp_cache_close(tls_ocsp_cache_t *cache) {
1420 pr_trace_msg(trace_channel, 9, "closing Redis ocsp cache %p", cache);
1421
1422 if (cache != NULL &&
1423 cache->cache_pool != NULL) {
1424
1425 /* We do NOT destroy the cache_pool here or close the redis connection;
1426 * both were created at daemon startup, and should live as long as
1427 * the daemon lives.
1428 */
1429
1430 if (ocspcache_resp_list != NULL) {
1431 register unsigned int i;
1432 struct ocspcache_large_entry *entries;
1433
1434 entries = ocspcache_resp_list->elts;
1435 for (i = 0; i < ocspcache_resp_list->nelts; i++) {
1436 struct ocspcache_large_entry *entry;
1437
1438 entry = &(entries[i]);
1439 pr_memscrub(entry->resp_der, entry->resp_derlen);
1440 entry->resp_derlen = 0;
1441 pr_memscrub(entry->fingerprint, entry->fingerprint_len);
1442 entry->fingerprint_len = 0;
1443 entry->age = 0;
1444 }
1445
1446 clear_array(ocspcache_resp_list);
1447 }
1448 }
1449
1450 return 0;
1451 }
1452
ocsp_cache_add_large_resp(tls_ocsp_cache_t * cache,const char * fingerprint,OCSP_RESPONSE * resp,time_t resp_age)1453 static int ocsp_cache_add_large_resp(tls_ocsp_cache_t *cache,
1454 const char *fingerprint, OCSP_RESPONSE *resp, time_t resp_age) {
1455 struct ocspcache_large_entry *entry = NULL;
1456 int resp_derlen;
1457 unsigned char *ptr;
1458
1459 resp_derlen = i2d_OCSP_RESPONSE(resp, NULL);
1460 if (resp_derlen > TLS_MAX_OCSP_RESPONSE_SIZE) {
1461 const char *exceeds_key = ocspcache_keys[OCSPCACHE_KEY_EXCEEDS].key,
1462 *max_len_key = ocspcache_keys[OCSPCACHE_KEY_MAX_LEN].key;
1463 void *value = NULL;
1464 size_t valuesz = 0;
1465 pool *tmp_pool;
1466
1467 if (pr_redis_incr(ocsp_redis, &tls_redis_module, exceeds_key, 1,
1468 NULL) < 0) {
1469 pr_trace_msg(trace_channel, 2,
1470 "error incrementing '%s' value: %s", exceeds_key, strerror(errno));
1471 }
1472
1473 /* XXX Yes, this is subject to race conditions; other proftpd servers
1474 * might also be modifying this value in Redis. Oh well.
1475 */
1476
1477 tmp_pool = make_sub_pool(cache->cache_pool);
1478 value = pr_redis_get(tmp_pool, ocsp_redis, &tls_redis_module, max_len_key,
1479 &valuesz);
1480 if (value != NULL) {
1481 uint64_t max_len;
1482
1483 memcpy(&max_len, value, valuesz);
1484 if ((uint64_t) resp_derlen > max_len) {
1485 if (pr_redis_set(ocsp_redis, &tls_redis_module, max_len_key, &max_len,
1486 sizeof(max_len), 0) < 0) {
1487 pr_trace_msg(trace_channel, 2,
1488 "error setting '%s' value: %s", max_len_key, strerror(errno));
1489 }
1490 }
1491
1492 } else {
1493 pr_trace_msg(trace_channel, 2,
1494 "error getting '%s' value: %s", max_len_key, strerror(errno));
1495 }
1496
1497 destroy_pool(tmp_pool);
1498 }
1499
1500 if (ocspcache_resp_list != NULL) {
1501 register unsigned int i;
1502 struct ocspcache_large_entry *entries;
1503 time_t now;
1504 int ok = FALSE;
1505
1506 /* Look for any expired sessions in the list to overwrite/reuse. */
1507 entries = ocspcache_resp_list->elts;
1508 time(&now);
1509 for (i = 0; i < ocspcache_resp_list->nelts; i++) {
1510 entry = &(entries[i]);
1511
1512 if (entry->age > (now - 3600)) {
1513 /* This entry has expired; clear and reuse its slot. */
1514 entry->age = 0;
1515 pr_memscrub(entry->resp_der, entry->resp_derlen);
1516 entry->resp_derlen = 0;
1517 pr_memscrub(entry->fingerprint, entry->fingerprint_len);
1518 entry->fingerprint_len = 0;
1519
1520 ok = TRUE;
1521 break;
1522 }
1523 }
1524
1525 if (!ok) {
1526 /* We didn't find an open slot in the list. Need to add one. */
1527 entry = push_array(ocspcache_resp_list);
1528 }
1529
1530 } else {
1531 ocspcache_resp_list = make_array(cache->cache_pool, 1,
1532 sizeof(struct ocspcache_large_entry));
1533 entry = push_array(ocspcache_resp_list);
1534 }
1535
1536 entry->age = resp_age;
1537 entry->fingerprint_len = strlen(fingerprint);
1538 entry->fingerprint = pstrdup(cache->cache_pool, fingerprint);
1539 entry->resp_derlen = resp_derlen;
1540 entry->resp_der = ptr = palloc(cache->cache_pool, resp_derlen);
1541 i2d_OCSP_RESPONSE(resp, &ptr);
1542
1543 return 0;
1544 }
1545
ocsp_cache_add(tls_ocsp_cache_t * cache,const char * fingerprint,OCSP_RESPONSE * resp,time_t resp_age)1546 static int ocsp_cache_add(tls_ocsp_cache_t *cache, const char *fingerprint,
1547 OCSP_RESPONSE *resp, time_t resp_age) {
1548 struct ocspcache_entry entry;
1549 int resp_derlen;
1550 unsigned char *ptr;
1551
1552 pr_trace_msg(trace_channel, 9, "adding response to Redis ocsp cache %p",
1553 cache);
1554
1555 /* First we need to find out how much space is needed for the serialized
1556 * response data. There is no known maximum size for OCSP response data;
1557 * this module is currently designed to allow only up to a certain size.
1558 */
1559 resp_derlen = i2d_OCSP_RESPONSE(resp, NULL);
1560 if (resp_derlen > TLS_MAX_OCSP_RESPONSE_SIZE) {
1561 pr_trace_msg(trace_channel, 2,
1562 "length of serialized OCSP response data (%d) exceeds maximum size (%u), "
1563 "unable to add to shared Redis, adding to list", resp_derlen,
1564 TLS_MAX_OCSP_RESPONSE_SIZE);
1565
1566 /* Instead of rejecting the add here, we add the response to a "large
1567 * response" list. Thus the large response would still be cached per
1568 * process and will not be lost.
1569 */
1570
1571 return ocsp_cache_add_large_resp(cache, fingerprint, resp, resp_age);
1572 }
1573
1574 entry.age = resp_age;
1575 entry.resp_derlen = resp_derlen;
1576 ptr = entry.resp_der;
1577 i2d_OCSP_RESPONSE(resp, &ptr);
1578
1579 if (ocsp_cache_redis_entry_set(cache->cache_pool, fingerprint, &entry) < 0) {
1580 pr_trace_msg(trace_channel, 2,
1581 "error adding response to Redis: %s", strerror(errno));
1582
1583 /* Add this response to the "large response" list instead as a fallback. */
1584 return ocsp_cache_add_large_resp(cache, fingerprint, resp, resp_age);
1585
1586 } else {
1587 const char *key = ocspcache_keys[OCSPCACHE_KEY_STORES].key;
1588
1589 if (pr_redis_incr(ocsp_redis, &tls_redis_module, key, 1, NULL) < 0) {
1590 pr_trace_msg(trace_channel, 2,
1591 "error incrementing '%s' value: %s", key, strerror(errno));
1592 }
1593 }
1594
1595 return 0;
1596 }
1597
ocsp_cache_get(tls_ocsp_cache_t * cache,const char * fingerprint,time_t * resp_age)1598 static OCSP_RESPONSE *ocsp_cache_get(tls_ocsp_cache_t *cache,
1599 const char *fingerprint, time_t *resp_age) {
1600 struct ocspcache_entry entry;
1601 OCSP_RESPONSE *resp = NULL;
1602 size_t fingerprint_len;
1603 const unsigned char *ptr;
1604
1605 pr_trace_msg(trace_channel, 9, "getting response from Redis ocsp cache %p",
1606 cache);
1607
1608 fingerprint_len = strlen(fingerprint);
1609
1610 /* Look for the requested response in the "large response" list first. */
1611 if (ocspcache_resp_list != NULL) {
1612 register unsigned int i;
1613 struct ocspcache_large_entry *entries;
1614
1615 entries = ocspcache_resp_list->elts;
1616 for (i = 0; i < ocspcache_resp_list->nelts; i++) {
1617 struct ocspcache_large_entry *large_entry;
1618
1619 large_entry = &(entries[i]);
1620 if (large_entry->fingerprint_len > 0 &&
1621 large_entry->fingerprint_len == fingerprint_len &&
1622 memcmp(large_entry->fingerprint, fingerprint, fingerprint_len) == 0) {
1623 ptr = large_entry->resp_der;
1624 resp = d2i_OCSP_RESPONSE(NULL, &ptr, large_entry->resp_derlen);
1625 if (resp == NULL) {
1626 pr_trace_msg(trace_channel, 2,
1627 "error retrieving response from ocsp cache: %s",
1628 redis_get_errors());
1629
1630 } else {
1631 *resp_age = large_entry->age;
1632 break;
1633 }
1634 }
1635 }
1636 }
1637
1638 if (resp) {
1639 return resp;
1640 }
1641
1642 if (ocsp_cache_redis_entry_get(cache->cache_pool, fingerprint, &entry) < 0) {
1643 return NULL;
1644 }
1645
1646 ptr = entry.resp_der;
1647 resp = d2i_OCSP_RESPONSE(NULL, &ptr, entry.resp_derlen);
1648 if (resp != NULL) {
1649 const char *key = ocspcache_keys[OCSPCACHE_KEY_HITS].key;
1650
1651 *resp_age = entry.age;
1652
1653 if (pr_redis_incr(ocsp_redis, &tls_redis_module, key, 1, NULL) < 0) {
1654 pr_trace_msg(trace_channel, 2,
1655 "error incrementing '%s' value: %s", key, strerror(errno));
1656 }
1657
1658 } else {
1659 const char *key = ocspcache_keys[OCSPCACHE_KEY_ERRORS].key;
1660
1661 pr_trace_msg(trace_channel, 2,
1662 "error retrieving response from ocsp cache: %s", redis_get_errors());
1663
1664 if (pr_redis_incr(ocsp_redis, &tls_redis_module, key, 1, NULL) < 0) {
1665 pr_trace_msg(trace_channel, 2,
1666 "error incrementing '%s' value: %s", key, strerror(errno));
1667 }
1668 }
1669
1670 if (resp == NULL) {
1671 const char *key = ocspcache_keys[OCSPCACHE_KEY_MISSES].key;
1672
1673 if (pr_redis_incr(ocsp_redis, &tls_redis_module, key, 1, NULL) < 0) {
1674 pr_trace_msg(trace_channel, 2,
1675 "error incrementing '%s' value: %s", key, strerror(errno));
1676 }
1677
1678 errno = ENOENT;
1679 }
1680
1681 return resp;
1682 }
1683
ocsp_cache_delete(tls_ocsp_cache_t * cache,const char * fingerprint)1684 static int ocsp_cache_delete(tls_ocsp_cache_t *cache,
1685 const char *fingerprint) {
1686 const char *key = ocspcache_keys[OCSPCACHE_KEY_DELETES].key;
1687 int res;
1688 size_t fingerprint_len;
1689
1690 pr_trace_msg(trace_channel, 9, "deleting response from Redis ocsp cache %p",
1691 cache);
1692
1693 fingerprint_len = strlen(fingerprint);
1694
1695 /* Look for the requested response in the "large response" list first. */
1696 if (ocspcache_resp_list != NULL) {
1697 register unsigned int i;
1698 struct ocspcache_large_entry *entries;
1699
1700 entries = ocspcache_resp_list->elts;
1701 for (i = 0; i < ocspcache_resp_list->nelts; i++) {
1702 struct ocspcache_large_entry *entry;
1703
1704 entry = &(entries[i]);
1705 if (entry->fingerprint_len == fingerprint_len &&
1706 memcmp(entry->fingerprint, fingerprint, fingerprint_len) == 0) {
1707
1708 pr_memscrub(entry->resp_der, entry->resp_derlen);
1709 entry->resp_derlen = 0;
1710 pr_memscrub(entry->fingerprint, entry->fingerprint_len);
1711 entry->fingerprint_len = 0;
1712 entry->age = 0;
1713
1714 return 0;
1715 }
1716 }
1717 }
1718
1719 res = ocsp_cache_redis_entry_delete(cache->cache_pool, fingerprint);
1720 if (res < 0) {
1721 return -1;
1722 }
1723
1724 /* Don't forget to update the stats. */
1725
1726 if (pr_redis_incr(ocsp_redis, &tls_redis_module, key, 1, NULL) < 0) {
1727 pr_trace_msg(trace_channel, 2,
1728 "error incrementing '%s' value: %s", key, strerror(errno));
1729 }
1730
1731 return res;
1732 }
1733
ocsp_cache_clear(tls_ocsp_cache_t * cache)1734 static int ocsp_cache_clear(tls_ocsp_cache_t *cache) {
1735 register unsigned int i;
1736 int res = 0;
1737
1738 if (ocsp_redis == NULL) {
1739 pr_trace_msg(trace_channel, 9, "missing required Redis connection");
1740 errno = EINVAL;
1741 return -1;
1742 }
1743
1744 pr_trace_msg(trace_channel, 9, "clearing Redis ocsp cache %p", cache);
1745
1746 if (ocspcache_resp_list != NULL) {
1747 struct ocspcache_large_entry *entries;
1748
1749 entries = ocspcache_resp_list->elts;
1750 for (i = 0; i < ocspcache_resp_list->nelts; i++) {
1751 struct ocspcache_large_entry *entry;
1752
1753 entry = &(entries[i]);
1754 entry->age = 0;
1755 pr_memscrub(entry->resp_der, entry->resp_derlen);
1756 entry->resp_derlen = 0;
1757 pr_memscrub(entry->fingerprint, entry->fingerprint_len);
1758 entry->fingerprint_len = 0;
1759 }
1760 }
1761
1762 /* XXX iterate through keys, kremoving any "mod_tls_redis" prefixed keys */
1763
1764 return res;
1765 }
1766
ocsp_cache_remove(tls_ocsp_cache_t * cache)1767 static int ocsp_cache_remove(tls_ocsp_cache_t *cache) {
1768 int res;
1769
1770 pr_trace_msg(trace_channel, 9, "removing Redis ocsp cache %p", cache);
1771
1772 res = ocsp_cache_clear(cache);
1773 /* XXX close Redis conn */
1774
1775 return res;
1776 }
1777
ocsp_cache_status(tls_ocsp_cache_t * cache,void (* statusf)(void *,const char *,...),void * arg,int flags)1778 static int ocsp_cache_status(tls_ocsp_cache_t *cache,
1779 void (*statusf)(void *, const char *, ...), void *arg, int flags) {
1780 register unsigned int i;
1781 pool *tmp_pool;
1782
1783 pr_trace_msg(trace_channel, 9, "checking Redis ocsp cache %p", cache);
1784
1785 tmp_pool = make_sub_pool(permanent_pool);
1786
1787 statusf(arg, "%s", "Redis OCSP response cache provided by "
1788 MOD_TLS_REDIS_VERSION);
1789 statusf(arg, "%s", "");
1790 statusf(arg, "Redis server: ");
1791
1792 for (i = 0; ocspcache_keys[i].key != NULL; i++) {
1793 const char *key, *desc;
1794 void *value = NULL;
1795 size_t valuesz = 0;
1796
1797 key = ocspcache_keys[i].key;
1798 desc = ocspcache_keys[i].desc;
1799
1800 value = pr_redis_get(tmp_pool, ocsp_redis, &tls_redis_module, key,
1801 &valuesz);
1802 if (value != NULL) {
1803 uint64_t num = 0;
1804 memcpy(&num, value, valuesz);
1805 statusf(arg, "%s: %lu", desc, (unsigned long) num);
1806 }
1807 }
1808
1809 /* XXX run stats on Redis servers? */
1810
1811 destroy_pool(tmp_pool);
1812 return 0;
1813 }
1814 #endif /* PR_USE_OPENSSL_OCSP */
1815
1816 /* Event Handlers
1817 */
1818
1819 #if defined(PR_SHARED_MODULE)
tls_redis_mod_unload_ev(const void * event_data,void * user_data)1820 static void tls_redis_mod_unload_ev(const void *event_data, void *user_data) {
1821 if (strcmp("mod_tls_redis.c", (const char *) event_data) == 0) {
1822 pr_event_unregister(&tls_redis_module, NULL, NULL);
1823 tls_sess_cache_unregister("redis");
1824 # if defined(PR_USE_OPENSSL_OCSP)
1825 tls_ocsp_cache_unregister("redis");
1826 # endif /* PR_USE_OPENSSL_OCSP */
1827
1828 if (sess_redis != NULL) {
1829 (void) pr_redis_conn_destroy(sess_redis);
1830 sess_redis = NULL;
1831 }
1832
1833 if (ocsp_redis != NULL) {
1834 (void) pr_redis_conn_destroy(ocsp_redis);
1835 ocsp_redis = NULL;
1836 }
1837 }
1838 }
1839 #endif /* !PR_SHARED_MODULE */
1840
1841 /* Initialization functions
1842 */
1843
tls_redis_init(void)1844 static int tls_redis_init(void) {
1845 #if defined(PR_SHARED_MODULE)
1846 pr_event_register(&tls_redis_module, "core.module-unload",
1847 tls_redis_mod_unload_ev, NULL);
1848 #endif /* !PR_SHARED_MODULE */
1849
1850 /* Prepare our SSL session cache handler. */
1851 memset(&sess_cache, 0, sizeof(sess_cache));
1852
1853 sess_cache.cache_name = "redis";
1854 pr_pool_tag(sess_cache.cache_pool, MOD_TLS_REDIS_VERSION);
1855
1856 sess_cache.open = sess_cache_open;
1857 sess_cache.close = sess_cache_close;
1858 sess_cache.add = sess_cache_add;
1859 sess_cache.get = sess_cache_get;
1860 sess_cache.delete = sess_cache_delete;
1861 sess_cache.clear = sess_cache_clear;
1862 sess_cache.remove = sess_cache_remove;
1863 sess_cache.status = sess_cache_status;
1864
1865 #ifdef SSL_SESS_CACHE_NO_INTERNAL
1866 /* Take a chance, and inform OpenSSL that it does not need to use its own
1867 * internal session cache lookups/storage; using the external session cache
1868 * (i.e. us) will be enough.
1869 */
1870 sess_cache.cache_mode = SSL_SESS_CACHE_NO_INTERNAL;
1871 #endif
1872
1873 #if defined(PR_USE_OPENSSL_OCSP)
1874 /* Prepare our OCSP response cache handler. */
1875 memset(&ocsp_cache, 0, sizeof(ocsp_cache));
1876
1877 ocsp_cache.cache_name = "redis";
1878 pr_pool_tag(ocsp_cache.cache_pool, MOD_TLS_REDIS_VERSION);
1879
1880 ocsp_cache.open = ocsp_cache_open;
1881 ocsp_cache.close = ocsp_cache_close;
1882 ocsp_cache.add = ocsp_cache_add;
1883 ocsp_cache.get = ocsp_cache_get;
1884 ocsp_cache.delete = ocsp_cache_delete;
1885 ocsp_cache.clear = ocsp_cache_clear;
1886 ocsp_cache.remove = ocsp_cache_remove;
1887 ocsp_cache.status = ocsp_cache_status;
1888 #endif /* PR_USE_OPENSSL_OCSP */
1889
1890 #if defined(PR_USE_REDIS)
1891 if (tls_sess_cache_register("redis", &sess_cache) < 0) {
1892 pr_log_debug(DEBUG1, MOD_TLS_REDIS_VERSION
1893 ": notice: error registering 'redis' SSL session cache: %s",
1894 strerror(errno));
1895 return -1;
1896 }
1897
1898 # if defined(PR_USE_OPENSSL_OCSP)
1899 if (tls_ocsp_cache_register("redis", &ocsp_cache) < 0) {
1900 pr_log_debug(DEBUG1, MOD_TLS_REDIS_VERSION
1901 ": notice: error registering 'redis' OCSP response cache: %s",
1902 strerror(errno));
1903 return -1;
1904 }
1905 # endif /* PR_USE_OPENSSL_OCSP */
1906
1907 #else
1908 pr_log_debug(DEBUG1, MOD_TLS_REDIS_VERSION
1909 ": unable to register 'redis' SSL session cache: Redis support not enabled");
1910 # if defined(PR_USE_OPENSSL_OCSP)
1911 pr_log_debug(DEBUG1, MOD_TLS_REDIS_VERSION
1912 ": unable to register 'redis' OCSP response cache: Redis support not enabled");
1913 # endif /* PR_USE_OPENSSL_OCSP */
1914 #endif /* PR_USE_REDIS */
1915
1916 return 0;
1917 }
1918
tls_redis_sess_init(void)1919 static int tls_redis_sess_init(void) {
1920 /* Reset our Redis handles. */
1921
1922 /* XXX Are these necessary? */
1923
1924 #if 0
1925 if (sess_redis != NULL) {
1926 if (pr_redis_conn_clone(session.pool, sess_redis) < 0) {
1927 tls_log(MOD_TLS_REDIS_VERSION
1928 ": error resetting Redis handle: %s", strerror(errno));
1929 }
1930 }
1931
1932 #if defined(PR_USE_OPENSSL_OCSP)
1933 if (ocsp_redis != NULL) {
1934 if (pr_redis_conn_clone(session.pool, ocsp_redis) < 0) {
1935 tls_log(MOD_TLS_REDIS_VERSION
1936 ": error resetting Redis handle: %s", strerror(errno));
1937 }
1938 }
1939 #endif /* PR_USE_OPENSSL_OCSP */
1940 #endif
1941
1942 return 0;
1943 }
1944
1945 /* Module API tables
1946 */
1947
1948 module tls_redis_module = {
1949 NULL, NULL,
1950
1951 /* Module API version 2.0 */
1952 0x20,
1953
1954 /* Module name */
1955 "tls_redis",
1956
1957 /* Module configuration handler table */
1958 NULL,
1959
1960 /* Module command handler table */
1961 NULL,
1962
1963 /* Module authentication handler table */
1964 NULL,
1965
1966 /* Module initialization function */
1967 tls_redis_init,
1968
1969 /* Session initialization function */
1970 tls_redis_sess_init,
1971
1972 /* Module version */
1973 MOD_TLS_REDIS_VERSION
1974 };
1975