xref: /openbsd-src/usr.sbin/unbound/cachedb/redis.c (revision 9c7f0a49a49eaaf8a541e2c1c60150e851bc44d2)
1712b2f30Ssthen /*
2712b2f30Ssthen  * cachedb/redis.c - cachedb redis module
3712b2f30Ssthen  *
4712b2f30Ssthen  * Copyright (c) 2018, NLnet Labs. All rights reserved.
5712b2f30Ssthen  *
6712b2f30Ssthen  * This software is open source.
7712b2f30Ssthen  *
8712b2f30Ssthen  * Redistribution and use in source and binary forms, with or without
9712b2f30Ssthen  * modification, are permitted provided that the following conditions
10712b2f30Ssthen  * are met:
11712b2f30Ssthen  *
12712b2f30Ssthen  * Redistributions of source code must retain the above copyright notice,
13712b2f30Ssthen  * this list of conditions and the following disclaimer.
14712b2f30Ssthen  *
15712b2f30Ssthen  * Redistributions in binary form must reproduce the above copyright notice,
16712b2f30Ssthen  * this list of conditions and the following disclaimer in the documentation
17712b2f30Ssthen  * and/or other materials provided with the distribution.
18712b2f30Ssthen  *
19712b2f30Ssthen  * Neither the name of the NLNET LABS nor the names of its contributors may
20712b2f30Ssthen  * be used to endorse or promote products derived from this software without
21712b2f30Ssthen  * specific prior written permission.
22712b2f30Ssthen  *
23712b2f30Ssthen  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24712b2f30Ssthen  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25712b2f30Ssthen  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26712b2f30Ssthen  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27712b2f30Ssthen  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28712b2f30Ssthen  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29712b2f30Ssthen  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30712b2f30Ssthen  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31712b2f30Ssthen  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32712b2f30Ssthen  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33712b2f30Ssthen  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34712b2f30Ssthen  */
35712b2f30Ssthen 
36712b2f30Ssthen /**
37712b2f30Ssthen  * \file
38712b2f30Ssthen  *
39712b2f30Ssthen  * This file contains a module that uses the redis database to cache
40712b2f30Ssthen  * dns responses.
41712b2f30Ssthen  */
42712b2f30Ssthen 
43712b2f30Ssthen #include "config.h"
44712b2f30Ssthen #ifdef USE_CACHEDB
45712b2f30Ssthen #include "cachedb/redis.h"
46712b2f30Ssthen #include "cachedb/cachedb.h"
47712b2f30Ssthen #include "util/alloc.h"
48712b2f30Ssthen #include "util/config_file.h"
49712b2f30Ssthen #include "sldns/sbuffer.h"
50712b2f30Ssthen 
51712b2f30Ssthen #ifdef USE_REDIS
52712b2f30Ssthen #include "hiredis/hiredis.h"
53712b2f30Ssthen 
54712b2f30Ssthen struct redis_moddata {
55712b2f30Ssthen 	redisContext** ctxs;	/* thread-specific redis contexts */
56712b2f30Ssthen 	int numctxs;		/* number of ctx entries */
57712b2f30Ssthen 	const char* server_host; /* server's IP address or host name */
58712b2f30Ssthen 	int server_port;	 /* server's TCP port */
59437d2860Ssthen 	const char* server_path; /* server's unix path, or "", NULL if unused */
60437d2860Ssthen 	const char* server_password; /* server's AUTH password, or "", NULL if unused */
61712b2f30Ssthen 	struct timeval timeout;	 /* timeout for connection setup and commands */
62*9c7f0a49Ssthen 	int logical_db;		/* the redis logical database to use */
63712b2f30Ssthen };
64712b2f30Ssthen 
65d7b4a113Ssthen static redisReply* redis_command(struct module_env*, struct cachedb_env*,
66d7b4a113Ssthen 	const char*, const uint8_t*, size_t);
67d7b4a113Ssthen 
68*9c7f0a49Ssthen static void
moddata_clean(struct redis_moddata ** moddata)69*9c7f0a49Ssthen moddata_clean(struct redis_moddata** moddata) {
70*9c7f0a49Ssthen 	if(!moddata || !*moddata)
71*9c7f0a49Ssthen 		return;
72*9c7f0a49Ssthen 	if((*moddata)->ctxs) {
73*9c7f0a49Ssthen 		int i;
74*9c7f0a49Ssthen 		for(i = 0; i < (*moddata)->numctxs; i++) {
75*9c7f0a49Ssthen 			if((*moddata)->ctxs[i])
76*9c7f0a49Ssthen 				redisFree((*moddata)->ctxs[i]);
77*9c7f0a49Ssthen 		}
78*9c7f0a49Ssthen 		free((*moddata)->ctxs);
79*9c7f0a49Ssthen 	}
80*9c7f0a49Ssthen 	free(*moddata);
81*9c7f0a49Ssthen 	*moddata = NULL;
82*9c7f0a49Ssthen }
83*9c7f0a49Ssthen 
84712b2f30Ssthen static redisContext*
redis_connect(const struct redis_moddata * moddata)85712b2f30Ssthen redis_connect(const struct redis_moddata* moddata)
86712b2f30Ssthen {
87712b2f30Ssthen 	redisContext* ctx;
88712b2f30Ssthen 
89437d2860Ssthen 	if(moddata->server_path && moddata->server_path[0]!=0) {
90437d2860Ssthen 		ctx = redisConnectUnixWithTimeout(moddata->server_path,
91437d2860Ssthen 			moddata->timeout);
92437d2860Ssthen 	} else {
93712b2f30Ssthen 		ctx = redisConnectWithTimeout(moddata->server_host,
94712b2f30Ssthen 			moddata->server_port, moddata->timeout);
95437d2860Ssthen 	}
96712b2f30Ssthen 	if(!ctx || ctx->err) {
97712b2f30Ssthen 		const char *errstr = "out of memory";
98712b2f30Ssthen 		if(ctx)
99712b2f30Ssthen 			errstr = ctx->errstr;
100712b2f30Ssthen 		log_err("failed to connect to redis server: %s", errstr);
101712b2f30Ssthen 		goto fail;
102712b2f30Ssthen 	}
103712b2f30Ssthen 	if(redisSetTimeout(ctx, moddata->timeout) != REDIS_OK) {
104712b2f30Ssthen 		log_err("failed to set redis timeout");
105712b2f30Ssthen 		goto fail;
106712b2f30Ssthen 	}
107437d2860Ssthen 	if(moddata->server_password && moddata->server_password[0]!=0) {
108437d2860Ssthen 		redisReply* rep;
109437d2860Ssthen 		rep = redisCommand(ctx, "AUTH %s", moddata->server_password);
110437d2860Ssthen 		if(!rep || rep->type == REDIS_REPLY_ERROR) {
111437d2860Ssthen 			log_err("failed to authenticate with password");
112437d2860Ssthen 			freeReplyObject(rep);
113437d2860Ssthen 			goto fail;
114437d2860Ssthen 		}
115437d2860Ssthen 		freeReplyObject(rep);
116437d2860Ssthen 	}
117*9c7f0a49Ssthen 	if(moddata->logical_db > 0) {
118*9c7f0a49Ssthen 		redisReply* rep;
119*9c7f0a49Ssthen 		rep = redisCommand(ctx, "SELECT %d", moddata->logical_db);
120*9c7f0a49Ssthen 		if(!rep || rep->type == REDIS_REPLY_ERROR) {
121*9c7f0a49Ssthen 			log_err("failed to set logical database (%d)",
122*9c7f0a49Ssthen 				moddata->logical_db);
123*9c7f0a49Ssthen 			freeReplyObject(rep);
124*9c7f0a49Ssthen 			goto fail;
125*9c7f0a49Ssthen 		}
126*9c7f0a49Ssthen 		freeReplyObject(rep);
127*9c7f0a49Ssthen 	}
128437d2860Ssthen 	verbose(VERB_OPS, "Connection to Redis established");
129712b2f30Ssthen 	return ctx;
130712b2f30Ssthen 
131712b2f30Ssthen fail:
132712b2f30Ssthen 	if(ctx)
133712b2f30Ssthen 		redisFree(ctx);
134712b2f30Ssthen 	return NULL;
135712b2f30Ssthen }
136712b2f30Ssthen 
137712b2f30Ssthen static int
redis_init(struct module_env * env,struct cachedb_env * cachedb_env)138712b2f30Ssthen redis_init(struct module_env* env, struct cachedb_env* cachedb_env)
139712b2f30Ssthen {
140712b2f30Ssthen 	int i;
141712b2f30Ssthen 	struct redis_moddata* moddata = NULL;
142712b2f30Ssthen 
143437d2860Ssthen 	verbose(VERB_OPS, "Redis initialization");
144712b2f30Ssthen 
145712b2f30Ssthen 	moddata = calloc(1, sizeof(struct redis_moddata));
146712b2f30Ssthen 	if(!moddata) {
147712b2f30Ssthen 		log_err("out of memory");
148*9c7f0a49Ssthen 		goto fail;
149712b2f30Ssthen 	}
150712b2f30Ssthen 	moddata->numctxs = env->cfg->num_threads;
151712b2f30Ssthen 	moddata->ctxs = calloc(env->cfg->num_threads, sizeof(redisContext*));
152712b2f30Ssthen 	if(!moddata->ctxs) {
153712b2f30Ssthen 		log_err("out of memory");
154*9c7f0a49Ssthen 		goto fail;
155712b2f30Ssthen 	}
156712b2f30Ssthen 	/* note: server_host is a shallow reference to configured string.
157712b2f30Ssthen 	 * we don't have to free it in this module. */
158712b2f30Ssthen 	moddata->server_host = env->cfg->redis_server_host;
159712b2f30Ssthen 	moddata->server_port = env->cfg->redis_server_port;
160437d2860Ssthen 	moddata->server_path = env->cfg->redis_server_path;
161437d2860Ssthen 	moddata->server_password = env->cfg->redis_server_password;
162712b2f30Ssthen 	moddata->timeout.tv_sec = env->cfg->redis_timeout / 1000;
163712b2f30Ssthen 	moddata->timeout.tv_usec = (env->cfg->redis_timeout % 1000) * 1000;
164*9c7f0a49Ssthen 	moddata->logical_db = env->cfg->redis_logical_db;
165*9c7f0a49Ssthen 	for(i = 0; i < moddata->numctxs; i++) {
166*9c7f0a49Ssthen 		redisContext* ctx = redis_connect(moddata);
167*9c7f0a49Ssthen 		if(!ctx) {
168*9c7f0a49Ssthen 			log_err("redis_init: failed to init redis");
169*9c7f0a49Ssthen 			goto fail;
170*9c7f0a49Ssthen 		}
171*9c7f0a49Ssthen 		moddata->ctxs[i] = ctx;
172*9c7f0a49Ssthen 	}
173712b2f30Ssthen 	cachedb_env->backend_data = moddata;
174d7b4a113Ssthen 	if(env->cfg->redis_expire_records) {
175d7b4a113Ssthen 		redisReply* rep = NULL;
176d7b4a113Ssthen 		int redis_reply_type = 0;
177d7b4a113Ssthen 		/** check if setex command is supported */
178d7b4a113Ssthen 		rep = redis_command(env, cachedb_env,
179d7b4a113Ssthen 			"SETEX __UNBOUND_REDIS_CHECK__ 1 none", NULL, 0);
180d7b4a113Ssthen 		if(!rep) {
181d7b4a113Ssthen 			/** init failed, no response from redis server*/
182d7b4a113Ssthen 			log_err("redis_init: failed to init redis, the "
183d7b4a113Ssthen 				"redis-expire-records option requires the SETEX command "
184d7b4a113Ssthen 				"(redis >= 2.0.0)");
185*9c7f0a49Ssthen 			goto fail;
186d7b4a113Ssthen 		}
187d7b4a113Ssthen 		redis_reply_type = rep->type;
188d7b4a113Ssthen 		freeReplyObject(rep);
189d7b4a113Ssthen 		switch(redis_reply_type) {
190d7b4a113Ssthen 		case REDIS_REPLY_STATUS:
191d7b4a113Ssthen 			break;
192d7b4a113Ssthen 		default:
193d7b4a113Ssthen 			/** init failed, setex command not supported */
194d7b4a113Ssthen 			log_err("redis_init: failed to init redis, the "
195d7b4a113Ssthen 				"redis-expire-records option requires the SETEX command "
196d7b4a113Ssthen 				"(redis >= 2.0.0)");
197*9c7f0a49Ssthen 			goto fail;
198d7b4a113Ssthen 		}
199d7b4a113Ssthen 	}
200712b2f30Ssthen 	return 1;
201*9c7f0a49Ssthen 
202*9c7f0a49Ssthen fail:
203*9c7f0a49Ssthen 	moddata_clean(&moddata);
204*9c7f0a49Ssthen 	return 0;
205712b2f30Ssthen }
206712b2f30Ssthen 
207712b2f30Ssthen static void
redis_deinit(struct module_env * env,struct cachedb_env * cachedb_env)208712b2f30Ssthen redis_deinit(struct module_env* env, struct cachedb_env* cachedb_env)
209712b2f30Ssthen {
210712b2f30Ssthen 	struct redis_moddata* moddata = (struct redis_moddata*)
211712b2f30Ssthen 		cachedb_env->backend_data;
212712b2f30Ssthen 	(void)env;
213712b2f30Ssthen 
214437d2860Ssthen 	verbose(VERB_OPS, "Redis deinitialization");
215*9c7f0a49Ssthen 	moddata_clean(&moddata);
216712b2f30Ssthen }
217712b2f30Ssthen 
218712b2f30Ssthen /*
219712b2f30Ssthen  * Send a redis command and get a reply.  Unified so that it can be used for
220712b2f30Ssthen  * both SET and GET.  If 'data' is non-NULL the command is supposed to be
221712b2f30Ssthen  * SET and GET otherwise, but the implementation of this function is agnostic
222712b2f30Ssthen  * about the semantics (except for logging): 'command', 'data', and 'data_len'
223712b2f30Ssthen  * are opaquely passed to redisCommand().
224712b2f30Ssthen  * This function first checks whether a connection with a redis server has
225712b2f30Ssthen  * been established; if not it tries to set up a new one.
226712b2f30Ssthen  * It returns redisReply returned from redisCommand() or NULL if some low
227712b2f30Ssthen  * level error happens.  The caller is responsible to check the return value,
228712b2f30Ssthen  * if it's non-NULL, it has to free it with freeReplyObject().
229712b2f30Ssthen  */
230712b2f30Ssthen static redisReply*
redis_command(struct module_env * env,struct cachedb_env * cachedb_env,const char * command,const uint8_t * data,size_t data_len)231712b2f30Ssthen redis_command(struct module_env* env, struct cachedb_env* cachedb_env,
232712b2f30Ssthen 	const char* command, const uint8_t* data, size_t data_len)
233712b2f30Ssthen {
234712b2f30Ssthen 	redisContext* ctx;
235712b2f30Ssthen 	redisReply* rep;
236712b2f30Ssthen 	struct redis_moddata* d = (struct redis_moddata*)
237712b2f30Ssthen 		cachedb_env->backend_data;
238712b2f30Ssthen 
239712b2f30Ssthen 	/* We assume env->alloc->thread_num is a unique ID for each thread
240712b2f30Ssthen 	 * in [0, num-of-threads).  We could treat it as an error condition
241712b2f30Ssthen 	 * if the assumption didn't hold, but it seems to be a fundamental
242712b2f30Ssthen 	 * assumption throughout the unbound architecture, so we simply assert
243712b2f30Ssthen 	 * it. */
244712b2f30Ssthen 	log_assert(env->alloc->thread_num < d->numctxs);
245712b2f30Ssthen 	ctx = d->ctxs[env->alloc->thread_num];
246712b2f30Ssthen 
247712b2f30Ssthen 	/* If we've not established a connection to the server or we've closed
248712b2f30Ssthen 	 * it on a failure, try to re-establish a new one.   Failures will be
249712b2f30Ssthen 	 * logged in redis_connect(). */
250712b2f30Ssthen 	if(!ctx) {
251712b2f30Ssthen 		ctx = redis_connect(d);
252712b2f30Ssthen 		d->ctxs[env->alloc->thread_num] = ctx;
253712b2f30Ssthen 	}
254712b2f30Ssthen 	if(!ctx)
255712b2f30Ssthen 		return NULL;
256712b2f30Ssthen 
257712b2f30Ssthen 	/* Send the command and get a reply, synchronously. */
258712b2f30Ssthen 	rep = (redisReply*)redisCommand(ctx, command, data, data_len);
259712b2f30Ssthen 	if(!rep) {
260712b2f30Ssthen 		/* Once an error as a NULL-reply is returned the context cannot
261712b2f30Ssthen 		 * be reused and we'll need to set up a new connection. */
262712b2f30Ssthen 		log_err("redis_command: failed to receive a reply, "
263712b2f30Ssthen 			"closing connection: %s", ctx->errstr);
264712b2f30Ssthen 		redisFree(ctx);
265712b2f30Ssthen 		d->ctxs[env->alloc->thread_num] = NULL;
266712b2f30Ssthen 		return NULL;
267712b2f30Ssthen 	}
268712b2f30Ssthen 
269712b2f30Ssthen 	/* Check error in reply to unify logging in that case.
270712b2f30Ssthen 	 * The caller may perform context-dependent checks and logging. */
271712b2f30Ssthen 	if(rep->type == REDIS_REPLY_ERROR)
272712b2f30Ssthen 		log_err("redis: %s resulted in an error: %s",
273712b2f30Ssthen 			data ? "set" : "get", rep->str);
274712b2f30Ssthen 
275712b2f30Ssthen 	return rep;
276712b2f30Ssthen }
277712b2f30Ssthen 
278712b2f30Ssthen static int
redis_lookup(struct module_env * env,struct cachedb_env * cachedb_env,char * key,struct sldns_buffer * result_buffer)279712b2f30Ssthen redis_lookup(struct module_env* env, struct cachedb_env* cachedb_env,
280712b2f30Ssthen 	char* key, struct sldns_buffer* result_buffer)
281712b2f30Ssthen {
282712b2f30Ssthen 	redisReply* rep;
283712b2f30Ssthen 	char cmdbuf[4+(CACHEDB_HASHSIZE/8)*2+1]; /* "GET " + key */
284712b2f30Ssthen 	int n;
285712b2f30Ssthen 	int ret = 0;
286712b2f30Ssthen 
287712b2f30Ssthen 	verbose(VERB_ALGO, "redis_lookup of %s", key);
288712b2f30Ssthen 
289712b2f30Ssthen 	n = snprintf(cmdbuf, sizeof(cmdbuf), "GET %s", key);
290712b2f30Ssthen 	if(n < 0 || n >= (int)sizeof(cmdbuf)) {
291712b2f30Ssthen 		log_err("redis_lookup: unexpected failure to build command");
292712b2f30Ssthen 		return 0;
293712b2f30Ssthen 	}
294712b2f30Ssthen 
295712b2f30Ssthen 	rep = redis_command(env, cachedb_env, cmdbuf, NULL, 0);
296712b2f30Ssthen 	if(!rep)
297712b2f30Ssthen 		return 0;
298712b2f30Ssthen 	switch(rep->type) {
299712b2f30Ssthen 	case REDIS_REPLY_NIL:
300712b2f30Ssthen 		verbose(VERB_ALGO, "redis_lookup: no data cached");
301712b2f30Ssthen 		break;
302712b2f30Ssthen 	case REDIS_REPLY_STRING:
303712b2f30Ssthen 		verbose(VERB_ALGO, "redis_lookup found %d bytes",
304712b2f30Ssthen 			(int)rep->len);
305712b2f30Ssthen 		if((size_t)rep->len > sldns_buffer_capacity(result_buffer)) {
306712b2f30Ssthen 			log_err("redis_lookup: replied data too long: %lu",
307712b2f30Ssthen 				(size_t)rep->len);
308712b2f30Ssthen 			break;
309712b2f30Ssthen 		}
310712b2f30Ssthen 		sldns_buffer_clear(result_buffer);
311712b2f30Ssthen 		sldns_buffer_write(result_buffer, rep->str, rep->len);
312712b2f30Ssthen 		sldns_buffer_flip(result_buffer);
313712b2f30Ssthen 		ret = 1;
314712b2f30Ssthen 		break;
315712b2f30Ssthen 	case REDIS_REPLY_ERROR:
316712b2f30Ssthen 		break;		/* already logged */
317712b2f30Ssthen 	default:
318712b2f30Ssthen 		log_err("redis_lookup: unexpected type of reply for (%d)",
319712b2f30Ssthen 			rep->type);
320712b2f30Ssthen 		break;
321712b2f30Ssthen 	}
322712b2f30Ssthen 	freeReplyObject(rep);
323712b2f30Ssthen 	return ret;
324712b2f30Ssthen }
325712b2f30Ssthen 
326712b2f30Ssthen static void
redis_store(struct module_env * env,struct cachedb_env * cachedb_env,char * key,uint8_t * data,size_t data_len,time_t ttl)327712b2f30Ssthen redis_store(struct module_env* env, struct cachedb_env* cachedb_env,
328d7b4a113Ssthen 	char* key, uint8_t* data, size_t data_len, time_t ttl)
329712b2f30Ssthen {
330712b2f30Ssthen 	redisReply* rep;
331712b2f30Ssthen 	int n;
332d7b4a113Ssthen 	int set_ttl = (env->cfg->redis_expire_records &&
333d7b4a113Ssthen 		(!env->cfg->serve_expired || env->cfg->serve_expired_ttl > 0));
334d7b4a113Ssthen 	/* Supported commands:
335d7b4a113Ssthen 	 * - "SET " + key + " %b"
336d7b4a113Ssthen 	 * - "SETEX " + key + " " + ttl + " %b"
337d7b4a113Ssthen 	 */
338d7b4a113Ssthen 	char cmdbuf[6+(CACHEDB_HASHSIZE/8)*2+11+3+1];
339712b2f30Ssthen 
340d7b4a113Ssthen 	if (!set_ttl) {
341712b2f30Ssthen 		verbose(VERB_ALGO, "redis_store %s (%d bytes)", key, (int)data_len);
342712b2f30Ssthen 		/* build command to set to a binary safe string */
343712b2f30Ssthen 		n = snprintf(cmdbuf, sizeof(cmdbuf), "SET %s %%b", key);
344d7b4a113Ssthen 	} else {
345d7b4a113Ssthen 		/* add expired ttl time to redis ttl to avoid premature eviction of key */
346d7b4a113Ssthen 		ttl += env->cfg->serve_expired_ttl;
347d7b4a113Ssthen 		verbose(VERB_ALGO, "redis_store %s (%d bytes) with ttl %u",
348d7b4a113Ssthen 			key, (int)data_len, (uint32_t)ttl);
349d7b4a113Ssthen 		/* build command to set to a binary safe string */
350d7b4a113Ssthen 		n = snprintf(cmdbuf, sizeof(cmdbuf), "SETEX %s %u %%b", key,
351d7b4a113Ssthen 			(uint32_t)ttl);
352d7b4a113Ssthen 	}
353d7b4a113Ssthen 
354d7b4a113Ssthen 
355712b2f30Ssthen 	if(n < 0 || n >= (int)sizeof(cmdbuf)) {
356712b2f30Ssthen 		log_err("redis_store: unexpected failure to build command");
357712b2f30Ssthen 		return;
358712b2f30Ssthen 	}
359712b2f30Ssthen 
360712b2f30Ssthen 	rep = redis_command(env, cachedb_env, cmdbuf, data, data_len);
361712b2f30Ssthen 	if(rep) {
362712b2f30Ssthen 		verbose(VERB_ALGO, "redis_store set completed");
363712b2f30Ssthen 		if(rep->type != REDIS_REPLY_STATUS &&
364712b2f30Ssthen 			rep->type != REDIS_REPLY_ERROR) {
365712b2f30Ssthen 			log_err("redis_store: unexpected type of reply (%d)",
366712b2f30Ssthen 				rep->type);
367712b2f30Ssthen 		}
368712b2f30Ssthen 		freeReplyObject(rep);
369712b2f30Ssthen 	}
370712b2f30Ssthen }
371712b2f30Ssthen 
372712b2f30Ssthen struct cachedb_backend redis_backend = { "redis",
373712b2f30Ssthen 	redis_init, redis_deinit, redis_lookup, redis_store
374712b2f30Ssthen };
375712b2f30Ssthen #endif	/* USE_REDIS */
376712b2f30Ssthen #endif /* USE_CACHEDB */
377