Add more version checks for modules
[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_delete(UNUSED void *instance, void *handle)
43 {
44         REDISSOCK *dissocket = handle;
45
46         redisFree(dissocket->conn);
47
48         if (dissocket->reply) {
49                 freeReplyObject(dissocket->reply);
50                 dissocket->reply = NULL;
51         }
52
53         talloc_free(dissocket);
54         return 1;
55 }
56
57 static void *mod_conn_create(void *ctx)
58 {
59         REDIS_INST *inst = ctx;
60         REDISSOCK *dissocket = NULL;
61         redisContext *conn;
62         redisReply *reply = NULL;
63         char buffer[1024];
64
65         conn = redisConnect(inst->hostname, inst->port);
66         if (conn->err) return NULL;
67
68         if (inst->password) {
69                 snprintf(buffer, sizeof(buffer), "AUTH %s", inst->password);
70
71                 reply = redisCommand(conn, buffer);
72                 if (!reply) {
73                         ERROR("rlm_redis (%s): Failed to run AUTH",
74                                inst->xlat_name);
75                 do_close:
76                         if (reply) freeReplyObject(reply);
77                         redisFree(conn);
78                         return NULL;
79                 }
80
81
82                 switch (reply->type) {
83                 case REDIS_REPLY_STATUS:
84                         if (strcmp(reply->str, "OK") != 0) {
85                                 ERROR("rlm_redis (%s): Failed authentication: reply %s",
86                                        inst->xlat_name, reply->str);
87                                 goto do_close;
88                         }
89                         break;  /* else it's OK */
90
91                 default:
92                         ERROR("rlm_redis (%s): Unexpected reply to AUTH",
93                                inst->xlat_name);
94                         goto do_close;
95                 }
96         }
97
98         if (inst->database) {
99                 snprintf(buffer, sizeof(buffer), "SELECT %d", inst->database);
100
101                 reply = redisCommand(conn, buffer);
102                 if (!reply) {
103                         ERROR("rlm_redis (%s): Failed to run SELECT",
104                                inst->xlat_name);
105                         goto do_close;
106                 }
107
108
109                 switch (reply->type) {
110                 case REDIS_REPLY_STATUS:
111                         if (strcmp(reply->str, "OK") != 0) {
112                                 ERROR("rlm_redis (%s): Failed SELECT %d: reply %s",
113                                        inst->xlat_name, inst->database,
114                                        reply->str);
115                                 goto do_close;
116                         }
117                         break;  /* else it's OK */
118
119                 default:
120                         ERROR("rlm_redis (%s): Unexpected reply to SELECT",
121                                inst->xlat_name);
122                         goto do_close;
123                 }
124         }
125
126         dissocket = talloc_zero(inst, REDISSOCK);
127         dissocket->conn = conn;
128
129         return dissocket;
130 }
131
132 static ssize_t redis_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
133 {
134         REDIS_INST *inst = instance;
135         REDISSOCK *dissocket;
136         size_t ret = 0;
137         char *buffer_ptr;
138         char buffer[21];
139
140         dissocket = fr_connection_get(inst->pool);
141         if (!dissocket) return -1;
142
143         /* Query failed for some reason, release socket and return */
144         if (rlm_redis_query(&dissocket, inst, fmt, request) < 0) {
145                 goto release;
146         }
147
148         switch (dissocket->reply->type) {
149         case REDIS_REPLY_INTEGER:
150                 buffer_ptr = buffer;
151                 snprintf(buffer_ptr, sizeof(buffer), "%lld",
152                          dissocket->reply->integer);
153
154                 ret = strlen(buffer_ptr);
155                 break;
156
157         case REDIS_REPLY_STATUS:
158         case REDIS_REPLY_STRING:
159                 buffer_ptr = dissocket->reply->str;
160                 ret = dissocket->reply->len;
161                 break;
162
163         default:
164                 buffer_ptr = NULL;
165                 break;
166         }
167
168         if ((ret >= freespace) || (!buffer_ptr)) {
169                 RDEBUG("rlm_redis (%s): Can't write result, insufficient space or unsupported result\n",
170                        inst->xlat_name);
171                 ret = -1;
172                 goto release;
173         }
174
175         strlcpy(out, buffer_ptr, freespace);
176
177 release:
178         rlm_redis_finish_query(dissocket);
179         fr_connection_release(inst->pool, dissocket);
180
181         return ret;
182 }
183
184 /*
185  *      Only free memory we allocated.  The strings allocated via
186  *      cf_section_parse() do not need to be freed.
187  */
188 static int mod_detach(void *instance)
189 {
190         REDIS_INST *inst = instance;
191
192         fr_connection_pool_delete(inst->pool);
193
194         return 0;
195 }
196
197 /*
198  *      Query the redis database
199  */
200 int rlm_redis_query(REDISSOCK **dissocket_p, REDIS_INST *inst,
201                     char const *query, REQUEST *request)
202 {
203         REDISSOCK *dissocket;
204         int argc;
205         char *argv[MAX_REDIS_ARGS];
206         char argv_buf[MAX_QUERY_LEN];
207
208         if (!query || !*query || !inst || !dissocket_p) {
209                 return -1;
210         }
211
212         argc = rad_expand_xlat(request, query, MAX_REDIS_ARGS, argv, false,
213                                 sizeof(argv_buf), argv_buf);
214         if (argc <= 0)
215                 return -1;
216
217         dissocket = *dissocket_p;
218
219         DEBUG2("executing %s ...", argv[0]);
220         dissocket->reply = redisCommandArgv(dissocket->conn, argc, (char const **)(void **)argv, NULL);
221         if (!dissocket->reply) {
222                 RERROR("%s", dissocket->conn->errstr);
223
224                 dissocket = fr_connection_reconnect(inst->pool, dissocket);
225                 if (!dissocket) {
226                 error:
227                         *dissocket_p = NULL;
228                         return -1;
229                 }
230
231                 dissocket->reply = redisCommand(dissocket->conn, query);
232                 if (!dissocket->reply) {
233                         RERROR("Failed after re-connect");
234                         fr_connection_del(inst->pool, dissocket);
235                         goto error;
236                 }
237
238                 *dissocket_p = dissocket;
239         }
240
241         if (dissocket->reply->type == REDIS_REPLY_ERROR) {
242                 RERROR("Query failed, %s", query);
243                 return -1;
244         }
245
246         return 0;
247 }
248
249 /*
250  *      Clear the redis reply object if any
251  */
252 int rlm_redis_finish_query(REDISSOCK *dissocket)
253 {
254         if (!dissocket || !dissocket->reply) {
255                 return -1;
256         }
257
258         freeReplyObject(dissocket->reply);
259         dissocket->reply = NULL;
260         return 0;
261 }
262
263 static int mod_instantiate(CONF_SECTION *conf, void *instance)
264 {
265         static bool version_done;
266
267         REDIS_INST *inst = instance;
268
269         if (!version_done) {
270                 version_done = true;
271
272                 INFO("rlm_redis: libhiredis version: %i.%i.%i", HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH);
273         }
274
275         inst->xlat_name = cf_section_name2(conf);
276
277         if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
278
279         xlat_register(inst->xlat_name, redis_xlat, NULL, inst); /* FIXME! */
280
281         inst->pool = fr_connection_pool_init(conf, inst, mod_conn_create, NULL, mod_conn_delete, NULL);
282         if (!inst->pool) {
283                 return -1;
284         }
285
286         inst->redis_query = rlm_redis_query;
287         inst->redis_finish_query = rlm_redis_finish_query;
288
289         return 0;
290 }
291
292 module_t rlm_redis = {
293         RLM_MODULE_INIT,
294         "redis",
295         RLM_TYPE_THREAD_SAFE, /* type */
296         sizeof(REDIS_INST),     /* yuck */
297         module_config,
298         mod_instantiate, /* instantiation */
299         mod_detach, /* detach */
300         {
301                 NULL, /* authentication */
302                 NULL, /* authorization */
303                 NULL, /* preaccounting */
304                 NULL, /* accounting */
305                 NULL, /* checksimul */
306                 NULL, /* pre-proxy */
307                 NULL, /* post-proxy */
308                 NULL /* post-auth */
309         },
310 };