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.
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.
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
19 * @brief Driver for the REDIS noSQL key value stores.
21 * @copyright 2000,2006 The FreeRADIUS server project
22 * @copyright 2011 TekSavvy Solutions <gabe@teksavvy.com>
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
30 #include "rlm_redis.h"
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 },
39 { NULL, -1, 0, NULL, NULL} /* end the list */
42 static int _mod_conn_free(REDISSOCK *dissocket)
44 redisFree(dissocket->conn);
46 if (dissocket->reply) {
47 freeReplyObject(dissocket->reply);
48 dissocket->reply = NULL;
54 static void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
56 REDIS_INST *inst = instance;
57 REDISSOCK *dissocket = NULL;
59 redisReply *reply = NULL;
62 conn = redisConnect(inst->hostname, inst->port);
63 if (conn->err) return NULL;
66 snprintf(buffer, sizeof(buffer), "AUTH %s", inst->password);
68 reply = redisCommand(conn, buffer);
70 ERROR("rlm_redis (%s): Failed to run AUTH", inst->xlat_name);
73 if (reply) freeReplyObject(reply);
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);
86 break; /* else it's OK */
89 ERROR("rlm_redis (%s): Unexpected reply to AUTH",
96 snprintf(buffer, sizeof(buffer), "SELECT %d", inst->database);
98 reply = redisCommand(conn, buffer);
100 ERROR("rlm_redis (%s): Failed to run SELECT",
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,
114 break; /* else it's OK */
117 ERROR("rlm_redis (%s): Unexpected reply to SELECT",
123 dissocket = talloc_zero(ctx, REDISSOCK);
124 dissocket->conn = conn;
125 talloc_set_destructor(dissocket, _mod_conn_free);
130 static ssize_t redis_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace)
132 REDIS_INST *inst = instance;
133 REDISSOCK *dissocket;
138 dissocket = fr_connection_get(inst->pool);
139 if (!dissocket) return -1;
141 /* Query failed for some reason, release socket and return */
142 if (rlm_redis_query(&dissocket, inst, fmt, request) < 0) {
146 switch (dissocket->reply->type) {
147 case REDIS_REPLY_INTEGER:
149 snprintf(buffer_ptr, sizeof(buffer), "%lld",
150 dissocket->reply->integer);
152 ret = strlen(buffer_ptr);
155 case REDIS_REPLY_STATUS:
156 case REDIS_REPLY_STRING:
157 buffer_ptr = dissocket->reply->str;
158 ret = dissocket->reply->len;
166 if ((ret >= freespace) || (!buffer_ptr)) {
167 RDEBUG("rlm_redis (%s): Can't write result, insufficient space or unsupported result\n",
173 strlcpy(out, buffer_ptr, freespace);
176 rlm_redis_finish_query(dissocket);
177 fr_connection_release(inst->pool, dissocket);
183 * Only free memory we allocated. The strings allocated via
184 * cf_section_parse() do not need to be freed.
186 static int mod_detach(void *instance)
188 REDIS_INST *inst = instance;
190 fr_connection_pool_delete(inst->pool);
196 * Query the redis database
198 int rlm_redis_query(REDISSOCK **dissocket_p, REDIS_INST *inst,
199 char const *query, REQUEST *request)
201 REDISSOCK *dissocket;
203 char *argv[MAX_REDIS_ARGS];
204 char argv_buf[MAX_QUERY_LEN];
206 if (!query || !*query || !inst || !dissocket_p) {
210 argc = rad_expand_xlat(request, query, MAX_REDIS_ARGS, argv, false,
211 sizeof(argv_buf), argv_buf);
215 dissocket = *dissocket_p;
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);
222 dissocket = fr_connection_reconnect(inst->pool, dissocket);
229 dissocket->reply = redisCommand(dissocket->conn, query);
230 if (!dissocket->reply) {
231 RERROR("Failed after re-connect");
232 fr_connection_del(inst->pool, dissocket);
236 *dissocket_p = dissocket;
239 if (dissocket->reply->type == REDIS_REPLY_ERROR) {
240 RERROR("Query failed, %s", query);
248 * Clear the redis reply object if any
250 int rlm_redis_finish_query(REDISSOCK *dissocket)
252 if (!dissocket || !dissocket->reply) {
256 freeReplyObject(dissocket->reply);
257 dissocket->reply = NULL;
261 static int mod_instantiate(CONF_SECTION *conf, void *instance)
263 static bool version_done;
265 REDIS_INST *inst = instance;
270 INFO("rlm_redis: libhiredis version: %i.%i.%i", HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH);
273 inst->xlat_name = cf_section_name2(conf);
275 if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
277 xlat_register(inst->xlat_name, redis_xlat, NULL, inst); /* FIXME! */
279 inst->pool = fr_connection_pool_init(conf, inst, mod_conn_create, NULL, NULL, NULL);
284 inst->redis_query = rlm_redis_query;
285 inst->redis_finish_query = rlm_redis_finish_query;
290 module_t rlm_redis = {
293 RLM_TYPE_THREAD_SAFE, /* type */
294 sizeof(REDIS_INST), /* yuck */
296 mod_instantiate, /* instantiation */
297 mod_detach, /* detach */
299 NULL, /* authentication */
300 NULL, /* authorization */
301 NULL, /* preaccounting */
302 NULL, /* accounting */
303 NULL, /* checksimul */
304 NULL, /* pre-proxy */
305 NULL, /* post-proxy */