1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) University of Cambridge 1995 - 2009 */
6 /* See the file NOTICE for conditions of use and distribution. */
10 #ifdef EXPERIMENTAL_REDIS
12 #include "lf_functions.h"
14 #include <hiredis/hiredis.h>
16 /* Structure and anchor for caching connections. */
17 typedef struct redis_connection {
18 struct redis_connection *next;
23 static redis_connection *redis_connections = NULL;
26 redis_open(uschar *filename, uschar **errmsg)
37 * XXX: Not sure how often this is called!
38 * Guess its called after every lookup which probably would mean to just
39 * not use the _tidy() function at all and leave with exim exiting to
42 while ((cn = redis_connections) != NULL) {
43 redis_connections = cn->next;
44 DEBUG(D_lookup) debug_printf("close REDIS connection: %s\n", cn->server);
45 redisFree(cn->handle);
49 /* This function is called from the find entry point to do the search for a
53 * query the query string
54 * server the server string
55 * resultptr where to store the result
56 * errmsg where to point an error message
57 * defer_break TRUE if no more servers are to be tried after DEFER
58 * do_cache set false if data is changed
60 * The server string is of the form "host/dbnumber/password". The host can be
61 * host:port. This string is in a nextinlist temporary buffer, so can be
64 * Returns: OK, FAIL, or DEFER
67 perform_redis_search(uschar *command, uschar *server, uschar **resultptr,
68 uschar **errmsg, BOOL *defer_break, BOOL *do_cache)
70 redisContext *redis_handle = NULL; /* Keep compilers happy */
71 redisReply *redis_reply = NULL;
72 redisReply *entry = NULL;
73 redisReply *tentry = NULL;
79 uschar *result = NULL;
80 uschar *server_copy = NULL;
85 * Disaggregate the parameters from the server argument.
86 * The order is host:port(socket)
87 * We can write to the string, since it is in a nextinlist temporary buffer.
88 * This copy is also used for debugging output.
90 memset(sdata, 0, sizeof(sdata)) /* Set all to NULL */;
91 for (i = 2; i > 0; i--) {
92 uschar *pp = Ustrrchr(server, '/');
94 *errmsg = string_sprintf("incomplete Redis server data: %s", (i == 2) ? server : server_copy);
100 if (i == 2) server_copy = string_copy(server); /* sans password */
102 sdata[0] = server; /* What's left at the start */
104 /* If the database or password is an empty string, set it NULL */
105 if (sdata[1][0] == 0) sdata[1] = NULL;
106 if (sdata[2][0] == 0) sdata[2] = NULL;
108 /* See if we have a cached connection to the server */
109 for (cn = redis_connections; cn != NULL; cn = cn->next) {
110 if (Ustrcmp(cn->server, server_copy) == 0) {
111 redis_handle = cn->handle;
118 uschar *socket = NULL;
120 /* int redis_err = REDIS_OK; */
122 if ((p = Ustrchr(sdata[0], '(')) != NULL) {
125 while (*p != 0 && *p != ')')
130 if ((p = Ustrchr(sdata[0], ':')) != NULL) {
134 port = Uatoi("6379");
137 if (Ustrchr(server, '/') != NULL) {
138 *errmsg = string_sprintf("unexpected slash in Redis server hostname: %s", sdata[0]);
144 debug_printf("REDIS new connection: host=%s port=%d socket=%s database=%s\n", sdata[0], port, socket, sdata[1]);
146 /* Get store for a new handle, initialize it, and connect to the server */
147 /* XXX: Use timeouts ? */
149 redis_handle = redisConnectUnix(CCS socket);
151 redis_handle = redisConnect(CCS server, port);
152 if (redis_handle == NULL) {
153 *errmsg = string_sprintf("REDIS connection failed");
154 *defer_break = FALSE;
158 /* Add the connection to the cache */
159 cn = store_get(sizeof(redis_connection));
160 cn->server = server_copy;
161 cn->handle = redis_handle;
162 cn->next = redis_connections;
163 redis_connections = cn;
166 debug_printf("REDIS using cached connection for %s\n", server_copy);
169 /* Authenticate if there is a password */
170 if(sdata[2] != NULL) {
171 if ((redis_reply = redisCommand(redis_handle, "AUTH %s", sdata[2])) == NULL) {
172 *errmsg = string_sprintf("REDIS Authentication failed: %s\n", redis_handle->errstr);
173 *defer_break = FALSE;
178 /* Select the database if there is a dbnumber passed */
179 if(sdata[1] != NULL) {
180 if ((redis_reply = redisCommand(redis_handle, "SELECT %s", sdata[1])) == NULL) {
181 *errmsg = string_sprintf("REDIS: Selecting database=%s failed: %s\n", sdata[1], redis_handle->errstr);
182 *defer_break = FALSE;
185 DEBUG(D_lookup) debug_printf("REDIS: Selecting database=%s\n", sdata[1]);
189 /* Run the command */
190 if ((redis_reply = redisCommand(redis_handle, CS command)) == NULL) {
191 *errmsg = string_sprintf("REDIS: query failed: %s\n", redis_handle->errstr);
192 *defer_break = FALSE;
196 switch (redis_reply->type) {
197 case REDIS_REPLY_ERROR:
198 *errmsg = string_sprintf("REDIS: lookup result failed: %s\n", redis_reply->str);
199 *defer_break = FALSE;
205 case REDIS_REPLY_NIL:
206 DEBUG(D_lookup) debug_printf("REDIS: query was not one that returned any data\n");
207 result = string_sprintf("");
213 case REDIS_REPLY_INTEGER:
214 ttmp = (redis_reply->integer == 1) ? US"true" : US"false";
215 result = string_cat(result, &ssize, &offset, US ttmp, Ustrlen(ttmp));
217 case REDIS_REPLY_STRING:
218 case REDIS_REPLY_STATUS:
219 result = string_cat(result, &ssize, &offset, US redis_reply->str, redis_reply->len);
221 case REDIS_REPLY_ARRAY:
223 /* NOTE: For now support 1 nested array result. If needed a limitless result can be parsed */
224 for (i = 0; i < redis_reply->elements; i++) {
225 entry = redis_reply->element[i];
228 result = string_cat(result, &ssize, &offset, US"\n", 1);
230 switch (entry->type) {
231 case REDIS_REPLY_INTEGER:
232 tmp = string_sprintf("%d", entry->integer);
233 result = string_cat(result, &ssize, &offset, US tmp, Ustrlen(tmp));
235 case REDIS_REPLY_STRING:
236 result = string_cat(result, &ssize, &offset, US entry->str, entry->len);
238 case REDIS_REPLY_ARRAY:
239 for (j = 0; j < entry->elements; j++) {
240 tentry = entry->element[j];
243 result = string_cat(result, &ssize, &offset, US"\n", 1);
245 switch (tentry->type) {
246 case REDIS_REPLY_INTEGER:
247 ttmp = string_sprintf("%d", tentry->integer);
248 result = string_cat(result, &ssize, &offset, US ttmp, Ustrlen(ttmp));
250 case REDIS_REPLY_STRING:
251 result = string_cat(result, &ssize, &offset, US tentry->str, tentry->len);
253 case REDIS_REPLY_ARRAY:
254 DEBUG(D_lookup) debug_printf("REDIS: result has nesting of arrays which is not supported. Ignoring!\n");
257 DEBUG(D_lookup) debug_printf("REDIS: result has unsupported type. Ignoring!\n");
263 DEBUG(D_lookup) debug_printf("REDIS: query returned unsupported type\n");
271 if (result == NULL) {
273 *errmsg = US"REDIS: no data found";
276 store_reset(result + offset + 1);
280 /* Free store for any result that was got; don't close the connection, as it is cached. */
281 if (redis_reply != NULL)
282 freeReplyObject(redis_reply);
284 /* Non-NULL result indicates a sucessful result */
285 if (result != NULL) {
289 DEBUG(D_lookup) debug_printf("%s\n", *errmsg);
290 /* NOTE: Required to close connection since it needs to be reopened */
291 return yield; /* FAIL or DEFER */
295 /*************************************************
297 *************************************************/
299 * See local README for interface description. The handle and filename
300 * arguments are not used. The code to loop through a list of servers while the
301 * query is deferred with a retryable error is now in a separate function that is
302 * shared with other noSQL lookups.
306 redis_find(void *handle __attribute__((unused)), uschar *filename __attribute__((unused)),
307 uschar *command, int length, uschar **result, uschar **errmsg, BOOL *do_cache)
309 return lf_sqlperform(US"Redis", US"redis_servers", redis_servers, command,
310 result, errmsg, do_cache, perform_redis_search);
313 /*************************************************
314 * Version reporting entry point *
315 *************************************************/
316 #include "../version.h"
319 redis_version_report(FILE *f)
321 fprintf(f, "Library version: REDIS: Compile: %d [%d]\n",
322 HIREDIS_MAJOR, HIREDIS_MINOR);
324 fprintf(f, " Exim version %s\n", EXIM_VERSION_STR);
328 /* These are the lookup_info blocks for this driver */
329 static lookup_info redis_lookup_info = {
330 US"redis", /* lookup name */
331 lookup_querystyle, /* query-style lookup */
332 redis_open, /* open function */
333 NULL, /* no check function */
334 redis_find, /* find function */
335 NULL, /* no close function */
336 redis_tidy, /* tidy function */
337 NULL, /* quoting function */
338 redis_version_report /* version reporting */
342 #define redis_lookup_module_info _lookup_module_info
343 #endif /* DYNLOOKUP */
345 static lookup_info *_lookup_list[] = { &redis_lookup_info };
346 lookup_module_info redis_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
348 #endif /* EXPERIMENTAL_REDIS */
349 /* End of lookups/redis.c */