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