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 */