Pass a threadsafe ctx into fr_connection_pool create callback
[freeradius.git] / src / modules / rlm_redis / rlm_redis.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License, version 2 if the
4  *   License as published by the Free Software Foundation.
5  *
6  *   This program is distributed in the hope that it will be useful,
7  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
8  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9  *   GNU General Public License for more details.
10  *
11  *   You should have received a copy of the GNU General Public License
12  *   along with this program; if not, write to the Free Software
13  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14  */
15
16 /**
17  * $Id$
18  * @file rlm_redis.c
19  * @brief Driver for the REDIS noSQL key value stores.
20  *
21  * @copyright 2000,2006  The FreeRADIUS server project
22  * @copyright 2011  TekSavvy Solutions <gabe@teksavvy.com>
23  */
24
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29
30 #include "rlm_redis.h"
31
32 static const CONF_PARSER module_config[] = {
33         { "hostname", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, REDIS_INST, hostname), NULL },
34         { "server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, REDIS_INST, hostname), NULL },
35         { "port", FR_CONF_OFFSET(PW_TYPE_SHORT, REDIS_INST, port), "6379" },
36         { "database", FR_CONF_OFFSET(PW_TYPE_INTEGER, REDIS_INST, database), "0" },
37         { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, REDIS_INST, password), NULL },
38
39         { NULL, -1, 0, NULL, NULL} /* end the list */
40 };
41
42 static int _mod_conn_free(REDISSOCK *dissocket)
43 {
44         redisFree(dissocket->conn);
45
46         if (dissocket->reply) {
47                 freeReplyObject(dissocket->reply);
48                 dissocket->reply = NULL;
49         }
50
51         return 0;
52 }
53
54 static void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
55 {
56         REDIS_INST *inst = instance;
57         REDISSOCK *dissocket = NULL;
58         redisContext *conn;
59         redisReply *reply = NULL;
60         char buffer[1024];
61
62         conn = redisConnect(inst->hostname, inst->port);
63         if (conn->err) return NULL;
64
65         if (inst->password) {
66                 snprintf(buffer, sizeof(buffer), "AUTH %s", inst->password);
67
68                 reply = redisCommand(conn, buffer);
69                 if (!reply) {
70                         ERROR("rlm_redis (%s): Failed to run AUTH", inst->xlat_name);
71
72                 do_close:
73                         if (reply) freeReplyObject(reply);
74                         redisFree(conn);
75                         return NULL;
76                 }
77
78
79                 switch (reply->type) {
80                 case REDIS_REPLY_STATUS:
81                         if (strcmp(reply->str, "OK") != 0) {
82                                 ERROR("rlm_redis (%s): Failed authentication: reply %s",
83                                        inst->xlat_name, reply->str);
84                                 goto do_close;
85                         }
86                         break;  /* else it's OK */
87
88                 default:
89                         ERROR("rlm_redis (%s): Unexpected reply to AUTH",
90                                inst->xlat_name);
91                         goto do_close;
92                 }
93         }
94
95         if (inst->database) {
96                 snprintf(buffer, sizeof(buffer), "SELECT %d", inst->database);
97
98                 reply = redisCommand(conn, buffer);
99                 if (!reply) {
100                         ERROR("rlm_redis (%s): Failed to run SELECT",
101                                inst->xlat_name);
102                         goto do_close;
103                 }
104
105
106                 switch (reply->type) {
107                 case REDIS_REPLY_STATUS:
108                         if (strcmp(reply->str, "OK") != 0) {
109                                 ERROR("rlm_redis (%s): Failed SELECT %d: reply %s",
110                                        inst->xlat_name, inst->database,
111                                        reply->str);
112                                 goto do_close;
113                         }
114                         break;  /* else it's OK */
115
116                 default:
117                         ERROR("rlm_redis (%s): Unexpected reply to SELECT",
118                                inst->xlat_name);
119                         goto do_close;
120                 }
121         }
122
123         dissocket = talloc_zero(ctx, REDISSOCK);
124         dissocket->conn = conn;
125         talloc_set_destructor(dissocket, _mod_conn_free);
126
127         return dissocket;
128 }
129
130 static ssize_t redis_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
131 {
132         REDIS_INST *inst = instance;
133         REDISSOCK *dissocket;
134         size_t ret = 0;
135         char *buffer_ptr;
136         char buffer[21];
137
138         dissocket = fr_connection_get(inst->pool);
139         if (!dissocket) return -1;
140
141         /* Query failed for some reason, release socket and return */
142         if (rlm_redis_query(&dissocket, inst, fmt, request) < 0) {
143                 goto release;
144         }
145
146         switch (dissocket->reply->type) {
147         case REDIS_REPLY_INTEGER:
148                 buffer_ptr = buffer;
149                 snprintf(buffer_ptr, sizeof(buffer), "%lld",
150                          dissocket->reply->integer);
151
152                 ret = strlen(buffer_ptr);
153                 break;
154
155         case REDIS_REPLY_STATUS:
156         case REDIS_REPLY_STRING:
157                 buffer_ptr = dissocket->reply->str;
158                 ret = dissocket->reply->len;
159                 break;
160
161         default:
162                 buffer_ptr = NULL;
163                 break;
164         }
165
166         if ((ret >= freespace) || (!buffer_ptr)) {
167                 RDEBUG("rlm_redis (%s): Can't write result, insufficient space or unsupported result\n",
168                        inst->xlat_name);
169                 ret = -1;
170                 goto release;
171         }
172
173         strlcpy(out, buffer_ptr, freespace);
174
175 release:
176         rlm_redis_finish_query(dissocket);
177         fr_connection_release(inst->pool, dissocket);
178
179         return ret;
180 }
181
182 /*
183  *      Only free memory we allocated.  The strings allocated via
184  *      cf_section_parse() do not need to be freed.
185  */
186 static int mod_detach(void *instance)
187 {
188         REDIS_INST *inst = instance;
189
190         fr_connection_pool_delete(inst->pool);
191
192         return 0;
193 }
194
195 /*
196  *      Query the redis database
197  */
198 int rlm_redis_query(REDISSOCK **dissocket_p, REDIS_INST *inst,
199                     char const *query, REQUEST *request)
200 {
201         REDISSOCK *dissocket;
202         int argc;
203         char *argv[MAX_REDIS_ARGS];
204         char argv_buf[MAX_QUERY_LEN];
205
206         if (!query || !*query || !inst || !dissocket_p) {
207                 return -1;
208         }
209
210         argc = rad_expand_xlat(request, query, MAX_REDIS_ARGS, argv, false,
211                                 sizeof(argv_buf), argv_buf);
212         if (argc <= 0)
213                 return -1;
214
215         dissocket = *dissocket_p;
216
217         DEBUG2("executing %s ...", argv[0]);
218         dissocket->reply = redisCommandArgv(dissocket->conn, argc, (char const **)(void **)argv, NULL);
219         if (!dissocket->reply) {
220                 RERROR("%s", dissocket->conn->errstr);
221
222                 dissocket = fr_connection_reconnect(inst->pool, dissocket);
223                 if (!dissocket) {
224                 error:
225                         *dissocket_p = NULL;
226                         return -1;
227                 }
228
229                 dissocket->reply = redisCommand(dissocket->conn, query);
230                 if (!dissocket->reply) {
231                         RERROR("Failed after re-connect");
232                         fr_connection_del(inst->pool, dissocket);
233                         goto error;
234                 }
235
236                 *dissocket_p = dissocket;
237         }
238
239         if (dissocket->reply->type == REDIS_REPLY_ERROR) {
240                 RERROR("Query failed, %s", query);
241                 return -1;
242         }
243
244         return 0;
245 }
246
247 /*
248  *      Clear the redis reply object if any
249  */
250 int rlm_redis_finish_query(REDISSOCK *dissocket)
251 {
252         if (!dissocket || !dissocket->reply) {
253                 return -1;
254         }
255
256         freeReplyObject(dissocket->reply);
257         dissocket->reply = NULL;
258         return 0;
259 }
260
261 static int mod_instantiate(CONF_SECTION *conf, void *instance)
262 {
263         static bool version_done;
264
265         REDIS_INST *inst = instance;
266
267         if (!version_done) {
268                 version_done = true;
269
270                 INFO("rlm_redis: libhiredis version: %i.%i.%i", HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH);
271         }
272
273         inst->xlat_name = cf_section_name2(conf);
274
275         if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
276
277         xlat_register(inst->xlat_name, redis_xlat, NULL, inst); /* FIXME! */
278
279         inst->pool = fr_connection_pool_init(conf, inst, mod_conn_create, NULL, NULL, NULL);
280         if (!inst->pool) {
281                 return -1;
282         }
283
284         inst->redis_query = rlm_redis_query;
285         inst->redis_finish_query = rlm_redis_finish_query;
286
287         return 0;
288 }
289
290 module_t rlm_redis = {
291         RLM_MODULE_INIT,
292         "redis",
293         RLM_TYPE_THREAD_SAFE, /* type */
294         sizeof(REDIS_INST),     /* yuck */
295         module_config,
296         mod_instantiate, /* instantiation */
297         mod_detach, /* detach */
298         {
299                 NULL, /* authentication */
300                 NULL, /* authorization */
301                 NULL, /* preaccounting */
302                 NULL, /* accounting */
303                 NULL, /* checksimul */
304                 NULL, /* pre-proxy */
305                 NULL, /* post-proxy */
306                 NULL /* post-auth */
307         },
308 };