2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * @brief Integrate FreeRADIUS with the Couchbase document database.
21 * @file rlm_couchbase.c
23 * @copyright 2013-2014 Aaron Hurt <ahurt@anbcs.com>
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/libradius.h>
30 #include <freeradius-devel/modules.h>
31 #include <freeradius-devel/rad_assert.h>
33 #include <libcouchbase/couchbase.h>
37 #include "couchbase.h"
38 #include "jsonc_missing.h"
41 * Module Configuration
43 static const CONF_PARSER module_config[] = {
44 { "acct_key", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_couchbase_t, acct_key), "radacct_%{%{Acct-Unique-Session-Id}:-%{Acct-Session-Id}}" },
45 { "doctype", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_couchbase_t, doctype), "radacct" },
46 { "server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_couchbase_t, server_raw), NULL },
47 { "bucket", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_couchbase_t, bucket), NULL },
48 { "password", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_couchbase_t, password), NULL },
49 { "expire", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_couchbase_t, expire), 0 },
50 { "user_key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_couchbase_t, user_key), "raduser_%{md5:%{tolower:%{%{Stripped-User-Name}:-%{User-Name}}}}" },
51 {NULL, -1, 0, NULL, NULL} /* end the list */
54 /* initialize couchbase connection */
55 static int mod_instantiate(CONF_SECTION *conf, void *instance) {
56 static bool version_done;
58 rlm_couchbase_t *inst = instance; /* our module instance */
62 INFO("rlm_couchbase: json-c version: %s", json_c_version());
63 INFO("rlm_couchbase: libcouchbase version: %s", lcb_get_version(NULL));
71 len = talloc_array_length(inst->server_raw);
72 server = p = talloc_array(inst, char, len);
73 for (i = 0; i < len; i++) {
74 switch (inst->server_raw[i]) {
78 /* Consume multiple separators occurring in sequence */
79 if (sep == true) continue;
87 *p++ = inst->server_raw[i];
93 inst->server = server;
97 if (mod_build_attribute_element_map(conf, inst) != 0) {
102 /* initiate connection pool */
103 inst->pool = fr_connection_pool_init(conf, inst, mod_conn_create, mod_conn_alive, mod_conn_delete, NULL);
105 /* check connection pool */
107 ERROR("rlm_couchbase: failed to initiate connection pool");
116 /* authorize users via couchbase */
117 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) {
118 rlm_couchbase_t *inst = instance; /* our module instance */
119 void *handle = NULL; /* connection pool handle */
120 char dockey[MAX_KEY_SIZE]; /* our document key */
121 lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */
123 /* assert packet as not null */
124 rad_assert(request->packet != NULL);
126 /* attempt to build document key */
127 if (radius_xlat(dockey, sizeof(dockey), request, inst->user_key, NULL, NULL) < 0) {
129 RERROR("could not find user key attribute (%s) in packet", inst->user_key);
131 return RLM_MODULE_FAIL;
135 handle = fr_connection_get(inst->pool);
138 if (!handle) return RLM_MODULE_FAIL;
140 /* set handle pointer */
141 rlm_couchbase_handle_t *handle_t = handle;
143 /* set couchbase instance */
144 lcb_t cb_inst = handle_t->handle;
147 cookie_t *cookie = handle_t->cookie;
152 memset(cookie, 0, sizeof(cookie_t));
155 RERROR("cookie not usable - possibly not allocated");
156 /* free connection */
158 fr_connection_release(inst->pool, handle);
161 return RLM_MODULE_FAIL;
164 /* reset cookie error status */
165 cookie->jerr = json_tokener_success;
168 cb_error = couchbase_get_key(cb_inst, cookie, dockey);
171 if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || cookie->jobj == NULL) {
173 RERROR("failed to fetch document or parse return");
174 /* free json object */
176 json_object_put(cookie->jobj);
180 fr_connection_release(inst->pool, handle);
183 return RLM_MODULE_FAIL;
187 RDEBUG("parsed user document == %s", json_object_to_json_string(cookie->jobj));
189 /* inject config value pairs defined in this json oblect */
190 mod_json_object_to_value_pairs(cookie->jobj, "config", request);
192 /* inject reply value pairs defined in this json oblect */
193 mod_json_object_to_value_pairs(cookie->jobj, "reply", request);
195 /* free json object */
197 json_object_put(cookie->jobj);
202 fr_connection_release(inst->pool, handle);
206 return RLM_MODULE_OK;
209 /* write accounting data to couchbase */
210 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request) {
211 rlm_couchbase_t *inst = instance; /* our module instance */
212 void *handle = NULL; /* connection pool handle */
213 VALUE_PAIR *vp; /* radius value pair linked list */
214 char dockey[MAX_KEY_SIZE]; /* our document key */
215 char document[MAX_VALUE_SIZE]; /* our document body */
216 char element[MAX_KEY_SIZE]; /* mapped radius attribute to element name */
217 int status = 0; /* account status type */
218 int docfound = 0; /* document found toggle */
219 lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */
221 /* assert packet as not null */
222 rad_assert(request->packet != NULL);
225 if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) {
227 RDEBUG("could not find status type in packet");
229 return RLM_MODULE_NOOP;
233 status = vp->vp_integer;
235 /* acknowledge the request but take no action */
236 if (status == PW_STATUS_ACCOUNTING_ON || status == PW_STATUS_ACCOUNTING_OFF) {
238 RDEBUG("handling accounting on/off request without action");
240 return RLM_MODULE_OK;
244 handle = fr_connection_get(inst->pool);
247 if (!handle) return RLM_MODULE_FAIL;
249 /* set handle pointer */
250 rlm_couchbase_handle_t *handle_t = handle;
252 /* set couchbase instance */
253 lcb_t cb_inst = handle_t->handle;
256 cookie_t *cookie = handle_t->cookie;
261 memset(cookie, 0, sizeof(cookie_t));
264 RERROR("cookie not usable - possibly not allocated");
265 /* free connection */
267 fr_connection_release(inst->pool, handle);
270 return RLM_MODULE_FAIL;
273 /* attempt to build document key */
274 if (radius_xlat(dockey, sizeof(dockey), request, inst->acct_key, NULL, NULL) < 0) {
276 RERROR("could not find accounting key attribute (%s) in packet", inst->acct_key);
279 fr_connection_release(inst->pool, handle);
282 return RLM_MODULE_NOOP;
285 /* init cookie error status */
286 cookie->jerr = json_tokener_success;
288 /* attempt to fetch document */
289 cb_error = couchbase_get_key(cb_inst, cookie, dockey);
292 if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success) {
294 RERROR("failed to execute get request or parse returned json object");
295 /* free json object */
297 json_object_put(cookie->jobj);
300 /* check cookie json object */
301 if (cookie->jobj != NULL) {
305 RDEBUG("parsed json body from couchbase: %s", json_object_to_json_string(cookie->jobj));
309 /* start json document if needed */
312 RDEBUG("document not found - creating new json document");
313 /* create new json object */
314 cookie->jobj = json_object_new_object();
315 /* set 'docType' element for new document */
316 json_object_object_add(cookie->jobj, "docType", json_object_new_string(inst->doctype));
317 /* set start and stop times ... ensure we always have these elements */
318 json_object_object_add(cookie->jobj, "startTimestamp", json_object_new_string("null"));
319 json_object_object_add(cookie->jobj, "stopTimestamp", json_object_new_string("null"));
322 /* status specific replacements for start/stop time */
324 case PW_STATUS_START:
326 if ((vp = pairfind(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) {
327 /* add to json object */
328 json_object_object_add(cookie->jobj, "startTimestamp", mod_value_pair_to_json_object(request, vp));
333 if ((vp = pairfind(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) {
334 /* add to json object */
335 json_object_object_add(cookie->jobj, "stopTimestamp", mod_value_pair_to_json_object(request, vp));
337 /* check start timestamp and adjust if needed */
338 mod_ensure_start_timestamp(cookie->jobj, request->packet->vps);
340 case PW_STATUS_ALIVE:
341 /* check start timestamp and adjust if needed */
342 mod_ensure_start_timestamp(cookie->jobj, request->packet->vps);
345 /* we shouldn't get here - free json object */
347 json_object_put(cookie->jobj);
349 /* release our connection handle */
351 fr_connection_release(inst->pool, handle);
353 /* return without doing anything */
354 return RLM_MODULE_NOOP;
357 /* loop through pairs and add to json document */
358 for (vp = request->packet->vps; vp; vp = vp->next) {
359 /* map attribute to element */
360 if (mod_attribute_to_element(vp->da->name, inst->map, &element) == 0) {
362 RDEBUG("mapped attribute %s => %s", vp->da->name, element);
363 /* add to json object with mapped name */
364 json_object_object_add(cookie->jobj, element, mod_value_pair_to_json_object(request, vp));
368 /* make sure we have enough room in our document buffer */
369 if ((unsigned int) json_object_get_string_len(cookie->jobj) > sizeof(document) - 1) {
370 /* this isn't good */
371 RERROR("could not write json document - insufficient buffer space");
372 /* free json output */
374 json_object_put(cookie->jobj);
378 fr_connection_release(inst->pool, handle);
381 return RLM_MODULE_FAIL;
383 /* copy json string to document */
384 strlcpy(document, json_object_to_json_string(cookie->jobj), sizeof(document));
385 /* free json output */
387 json_object_put(cookie->jobj);
392 RDEBUG("setting '%s' => '%s'", dockey, document);
394 /* store document/key in couchbase */
395 cb_error = couchbase_set_key(cb_inst, dockey, document, inst->expire);
398 if (cb_error != LCB_SUCCESS) {
399 RERROR("failed to store document (%s): %s (0x%x)", dockey, lcb_strerror(NULL, cb_error), cb_error);
404 fr_connection_release(inst->pool, handle);
408 return RLM_MODULE_OK;
411 /* free any memory we allocated */
412 static int mod_detach(void *instance) {
413 rlm_couchbase_t *inst = instance; /* instance struct */
415 /* free json object attribute map */
417 json_object_put(inst->map);
420 /* destroy connection pool */
422 fr_connection_pool_delete(inst->pool);
429 /* hook the module into freeradius */
430 module_t rlm_couchbase = {
433 RLM_TYPE_THREAD_SAFE, /* type */
434 sizeof(rlm_couchbase_t),
436 mod_instantiate, /* instantiation */
437 mod_detach, /* detach */
439 NULL, /* authentication */
440 mod_authorize, /* authorization */
441 NULL, /* preaccounting */
442 mod_accounting, /* accounting */
443 NULL, /* checksimul */
444 NULL, /* pre-proxy */
445 NULL, /* post-proxy */