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 * @author Aaron Hurt <ahurt@anbcs.com>
24 * @copyright 2013-2014 The FreeRADIUS Server Project.
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/libradius.h>
31 #include <freeradius-devel/modules.h>
32 #include <freeradius-devel/rad_assert.h>
34 #include <libcouchbase/couchbase.h>
37 #include "couchbase.h"
38 #include "jsonc_missing.h"
41 * Client Configuration
43 static const CONF_PARSER client_config[] = {
44 { "view", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_couchbase_t, client_view), "_design/client/_view/by_name" },
45 CONF_PARSER_TERMINATOR
49 * Module Configuration
51 static const CONF_PARSER module_config[] = {
52 { "server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_couchbase_t, server_raw), NULL },
53 { "bucket", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_couchbase_t, bucket), NULL },
54 { "password", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_couchbase_t, password), NULL },
55 #ifdef WITH_ACCOUNTING
56 { "acct_key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_couchbase_t, acct_key), "radacct_%{%{Acct-Unique-Session-Id}:-%{Acct-Session-Id}}" },
57 { "doctype", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_couchbase_t, doctype), "radacct" },
58 { "expire", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_couchbase_t, expire), 0 },
60 { "user_key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_couchbase_t, user_key), "raduser_%{md5:%{tolower:%{%{Stripped-User-Name}:-%{User-Name}}}}" },
61 { "read_clients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_couchbase_t, read_clients), NULL }, /* NULL defaults to "no" */
62 { "client", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) client_config },
63 #ifdef WITH_SESSION_MGMT
64 { "check_simul", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_couchbase_t, check_simul), NULL }, /* NULL defaults to "no" */
65 { "simul_view", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_couchbase_t, simul_view), "_design/acct/_view/by_user" },
66 { "simul_vkey", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_couchbase_t, simul_vkey), "%{tolower:%{%{Stripped-User-Name}:-%{User-Name}}}" },
67 { "verify_simul", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_couchbase_t, verify_simul), NULL }, /* NULL defaults to "no" */
69 CONF_PARSER_TERMINATOR
72 /** Initialize the rlm_couchbase module
74 * Intialize the module and create the initial Couchbase connection pool.
76 * @param conf The module configuration.
77 * @param instance The module instance.
78 * @return Returns 0 on success, -1 on error.
80 static int mod_instantiate(CONF_SECTION *conf, void *instance)
82 static bool version_done;
84 rlm_couchbase_t *inst = instance; /* our module instance */
88 INFO("rlm_couchbase: json-c version: %s", json_c_version());
89 INFO("rlm_couchbase: libcouchbase version: %s", lcb_get_version(NULL));
97 len = talloc_array_length(inst->server_raw);
98 server = p = talloc_array(inst, char, len);
99 for (i = 0; i < len; i++) {
100 switch (inst->server_raw[i]) {
104 /* Consume multiple separators occurring in sequence */
105 if (sep == true) continue;
113 *p++ = inst->server_raw[i];
119 inst->server = server;
123 if (mod_build_attribute_element_map(conf, inst) != 0) {
128 /* initiate connection pool */
129 inst->pool = fr_connection_pool_module_init(conf, inst, mod_conn_create, NULL, NULL);
131 /* check connection pool */
133 ERROR("rlm_couchbase: failed to initiate connection pool");
138 /* load clients if requested */
139 if (inst->read_clients) {
140 CONF_SECTION *cs, *map, *tmpl; /* conf section */
142 /* attempt to find client section */
143 cs = cf_section_sub_find(conf, "client");
145 ERROR("rlm_couchbase: failed to find client section while loading clients");
150 /* attempt to find attribute subsection */
151 map = cf_section_sub_find(cs, "attribute");
153 ERROR("rlm_couchbase: failed to find attribute subsection while loading clients");
158 tmpl = cf_section_sub_find(cs, "template");
161 DEBUG("rlm_couchbase: preparing to load client documents");
163 /* attempt to load clients */
164 if (mod_load_client_documents(inst, tmpl, map) != 0) {
174 /** Handle authorization requests using Couchbase document data
176 * Attempt to fetch the document assocaited with the requested user by
177 * using the deterministic key defined in the configuration. When a valid
178 * document is found it will be parsed and the containing value pairs will be
179 * injected into the request.
181 * @param instance The module instance.
182 * @param request The authorization request.
183 * @return Returns operation status (@p rlm_rcode_t).
185 static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
187 rlm_couchbase_t *inst = instance; /* our module instance */
188 rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */
189 char dockey[MAX_KEY_SIZE]; /* our document key */
190 lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */
191 rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */
193 /* assert packet as not null */
194 rad_assert(request->packet != NULL);
196 /* attempt to build document key */
197 if (radius_xlat(dockey, sizeof(dockey), request, inst->user_key, NULL, NULL) < 0) {
199 RERROR("could not find user key attribute (%s) in packet", inst->user_key);
201 return RLM_MODULE_FAIL;
205 handle = fr_connection_get(inst->pool);
208 if (!handle) return RLM_MODULE_FAIL;
210 /* set couchbase instance */
211 lcb_t cb_inst = handle->handle;
214 cookie_t *cookie = handle->cookie;
217 cb_error = couchbase_get_key(cb_inst, cookie, dockey);
220 if (cb_error != LCB_SUCCESS || !cookie->jobj) {
222 RERROR("failed to fetch document or parse return");
224 rcode = RLM_MODULE_FAIL;
230 RDEBUG3("parsed user document == %s", json_object_to_json_string(cookie->jobj));
232 /* inject config value pairs defined in this json oblect */
233 mod_json_object_to_value_pairs(cookie->jobj, "config", request);
235 /* inject reply value pairs defined in this json oblect */
236 mod_json_object_to_value_pairs(cookie->jobj, "reply", request);
240 /* free json object */
242 json_object_put(cookie->jobj);
248 fr_connection_release(inst->pool, handle);
255 #ifdef WITH_ACCOUNTING
256 /** Write accounting data to Couchbase documents
258 * Handle accounting requests and store the associated data into JSON documents
259 * in couchbase mapping attribute names to JSON element names per the module configuration.
261 * When an existing document already exists for the same accounting section the new attributes
262 * will be merged with the currently existing data. When conflicts arrise the new attribute
263 * value will replace or be added to the existing value.
265 * @param instance The module instance.
266 * @param request The accounting request object.
267 * @return Returns operation status (@p rlm_rcode_t).
269 static rlm_rcode_t mod_accounting(void *instance, REQUEST *request)
271 rlm_couchbase_t *inst = instance; /* our module instance */
272 rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */
273 rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */
274 VALUE_PAIR *vp; /* radius value pair linked list */
275 char dockey[MAX_KEY_SIZE]; /* our document key */
276 char document[MAX_VALUE_SIZE]; /* our document body */
277 char element[MAX_KEY_SIZE]; /* mapped radius attribute to element name */
278 int status = 0; /* account status type */
279 int docfound = 0; /* document found toggle */
280 lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */
282 /* assert packet as not null */
283 rad_assert(request->packet != NULL);
286 if ((vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) {
288 RDEBUG("could not find status type in packet");
290 return RLM_MODULE_NOOP;
294 status = vp->vp_integer;
296 /* acknowledge the request but take no action */
297 if (status == PW_STATUS_ACCOUNTING_ON || status == PW_STATUS_ACCOUNTING_OFF) {
299 RDEBUG("handling accounting on/off request without action");
301 return RLM_MODULE_OK;
305 handle = fr_connection_get(inst->pool);
308 if (!handle) return RLM_MODULE_FAIL;
310 /* set couchbase instance */
311 lcb_t cb_inst = handle->handle;
314 cookie_t *cookie = handle->cookie;
316 /* attempt to build document key */
317 if (radius_xlat(dockey, sizeof(dockey), request, inst->acct_key, NULL, NULL) < 0) {
319 RERROR("could not find accounting key attribute (%s) in packet", inst->acct_key);
321 rcode = RLM_MODULE_NOOP;
326 /* attempt to fetch document */
327 cb_error = couchbase_get_key(cb_inst, cookie, dockey);
329 /* check error and object */
330 if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) {
332 RERROR("failed to execute get request or parse returned json object");
333 /* free and reset json object */
335 json_object_put(cookie->jobj);
338 /* check cookie json object */
339 } else if (cookie->jobj) {
343 RDEBUG3("parsed json body from couchbase: %s", json_object_to_json_string(cookie->jobj));
346 /* start json document if needed */
349 RDEBUG("no existing document found - creating new json document");
350 /* create new json object */
351 cookie->jobj = json_object_new_object();
352 /* set 'docType' element for new document */
353 json_object_object_add(cookie->jobj, "docType", json_object_new_string(inst->doctype));
354 /* default startTimestamp and stopTimestamp to null values */
355 json_object_object_add(cookie->jobj, "startTimestamp", NULL);
356 json_object_object_add(cookie->jobj, "stopTimestamp", NULL);
359 /* status specific replacements for start/stop time */
361 case PW_STATUS_START:
363 if ((vp = fr_pair_find_by_num(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) {
364 /* add to json object */
365 json_object_object_add(cookie->jobj, "startTimestamp",
366 mod_value_pair_to_json_object(request, vp));
372 if ((vp = fr_pair_find_by_num(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) {
373 /* add to json object */
374 json_object_object_add(cookie->jobj, "stopTimestamp",
375 mod_value_pair_to_json_object(request, vp));
377 /* check start timestamp and adjust if needed */
378 mod_ensure_start_timestamp(cookie->jobj, request->packet->vps);
381 case PW_STATUS_ALIVE:
382 /* check start timestamp and adjust if needed */
383 mod_ensure_start_timestamp(cookie->jobj, request->packet->vps);
387 /* don't doing anything */
388 rcode = RLM_MODULE_NOOP;
393 /* loop through pairs and add to json document */
394 for (vp = request->packet->vps; vp; vp = vp->next) {
395 /* map attribute to element */
396 if (mod_attribute_to_element(vp->da->name, inst->map, &element) == 0) {
398 RDEBUG3("mapped attribute %s => %s", vp->da->name, element);
399 /* add to json object with mapped name */
400 json_object_object_add(cookie->jobj, element, mod_value_pair_to_json_object(request, vp));
404 /* copy json string to document and check size */
405 if (strlcpy(document, json_object_to_json_string(cookie->jobj), sizeof(document)) >= sizeof(document)) {
406 /* this isn't good */
407 RERROR("could not write json document - insufficient buffer space");
409 rcode = RLM_MODULE_FAIL;
415 RDEBUG3("setting '%s' => '%s'", dockey, document);
417 /* store document/key in couchbase */
418 cb_error = couchbase_set_key(cb_inst, dockey, document, inst->expire);
421 if (cb_error != LCB_SUCCESS) {
422 RERROR("failed to store document (%s): %s (0x%x)", dockey, lcb_strerror(NULL, cb_error), cb_error);
426 /* free and reset json object */
428 json_object_put(cookie->jobj);
432 /* release our connection handle */
434 fr_connection_release(inst->pool, handle);
442 #ifdef WITH_SESSION_MGMT
443 /** Check if a given user is already logged in.
445 * Process accounting data to determine if a user is already logged in. Sets request->simul_count
446 * to the current session count for this user.
448 * Check twice. If on the first pass the user exceeds his maximum number of logins, do a second
449 * pass and validate all logins by querying the terminal server.
451 * @param instance The module instance.
452 * @param request The checksimul request object.
453 * @return Returns operation status (@p rlm_rcode_t).
455 static rlm_rcode_t mod_checksimul(void *instance, REQUEST *request) {
456 rlm_couchbase_t *inst = instance; /* our module instance */
457 rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */
458 rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */
459 char vpath[256], vkey[MAX_KEY_SIZE]; /* view path and query key */
460 char docid[MAX_KEY_SIZE]; /* document id returned from view */
461 char error[512]; /* view error return */
462 int idx = 0; /* row array index counter */
463 char element[MAX_KEY_SIZE]; /* mapped radius attribute to element name */
464 lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */
465 json_object *json, *jval; /* json object holders */
466 json_object *jrows = NULL; /* json object to hold view rows */
467 VALUE_PAIR *vp; /* value pair */
468 uint32_t client_ip_addr = 0; /* current client ip address */
469 char const *client_cs_id = NULL; /* current client calling station id */
470 char *user_name = NULL; /* user name from accounting document */
471 char *session_id = NULL; /* session id from accounting document */
472 char *cs_id = NULL; /* calling station id from accounting document */
473 uint32_t nas_addr = 0; /* nas address from accounting document */
474 uint32_t nas_port = 0; /* nas port from accounting document */
475 uint32_t framed_ip_addr = 0; /* framed ip address from accounting document */
476 char framed_proto = 0; /* framed proto from accounting document */
477 int session_time = 0; /* session time from accounting document */
479 /* do nothing if this is not enabled */
480 if (inst->check_simul != true) {
481 RDEBUG3("mod_checksimul returning noop - not enabled");
482 return RLM_MODULE_NOOP;
485 /* ensure valid username in request */
486 if ((!request->username) || (request->username->vp_length == '\0')) {
487 RDEBUG3("mod_checksimul - invalid username");
488 return RLM_MODULE_INVALID;
491 /* attempt to build view key */
492 if (radius_xlat(vkey, sizeof(vkey), request, inst->simul_vkey, NULL, NULL) < 0) {
494 RERROR("could not find simultaneous use view key attribute (%s) in packet", inst->simul_vkey);
496 return RLM_MODULE_FAIL;
500 handle = fr_connection_get(inst->pool);
503 if (!handle) return RLM_MODULE_FAIL;
505 /* set couchbase instance */
506 lcb_t cb_inst = handle->handle;
509 cookie_t *cookie = handle->cookie;
511 /* build view path */
512 snprintf(vpath, sizeof(vpath), "%s?key=\"%s\"&stale=update_after",
513 inst->simul_view, vkey);
515 /* query view for document */
516 cb_error = couchbase_query_view(cb_inst, cookie, vpath, NULL);
518 /* check error and object */
519 if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) {
521 RERROR("failed to execute view request or parse return");
523 rcode = RLM_MODULE_FAIL;
529 RDEBUG3("cookie->jobj == %s", json_object_to_json_string(cookie->jobj));
531 /* check for error in json object */
532 if (json_object_object_get_ex(cookie->jobj, "error", &json)) {
533 /* build initial error buffer */
534 strlcpy(error, json_object_get_string(json), sizeof(error));
535 /* get error reason */
536 if (json_object_object_get_ex(cookie->jobj, "reason", &json)) {
538 strlcat(error, " - ", sizeof(error));
540 strlcat(error, json_object_get_string(json), sizeof(error));
543 RERROR("view request failed with error: %s", error);
545 rcode = RLM_MODULE_FAIL;
550 /* check for document id in return */
551 if (!json_object_object_get_ex(cookie->jobj, "rows", &json)) {
553 RERROR("failed to fetch rows from view payload");
555 rcode = RLM_MODULE_FAIL;
560 /* get and hold rows */
561 jrows = json_object_get(json);
563 /* free cookie object */
565 json_object_put(cookie->jobj);
569 /* check for valid row value */
570 if (!jrows || !json_object_is_type(jrows, json_type_array)) {
572 RERROR("no valid rows returned from view: %s", vpath);
574 rcode = RLM_MODULE_FAIL;
580 RDEBUG3("jrows == %s", json_object_to_json_string(jrows));
583 request->simul_count = json_object_array_length(jrows);
586 RDEBUG("found %d open sessions for %s", request->simul_count, request->username->vp_strvalue);
589 if (request->simul_count < request->simul_max) {
590 rcode = RLM_MODULE_OK;
595 * Current session count exceeds configured maximum.
596 * Continue on to verify the sessions if configured otherwise stop here.
598 if (inst->verify_simul != true) {
599 rcode = RLM_MODULE_OK;
604 RDEBUG("verifying session count");
606 /* reset the count */
607 request->simul_count = 0;
609 /* get client ip address for MPP detection below */
610 if ((vp = fr_pair_find_by_num(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) {
611 client_ip_addr = vp->vp_ipaddr;
614 /* get calling station id for MPP detection below */
615 if ((vp = fr_pair_find_by_num(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) {
616 client_cs_id = vp->vp_strvalue;
619 /* loop across all row elements */
620 for (idx = 0; idx < json_object_array_length(jrows); idx++) {
622 memset(docid, 0, sizeof(docid));
624 /* fetch current index */
625 json = json_object_array_get_idx(jrows, idx);
627 /* get document id */
628 if (json_object_object_get_ex(json, "id", &jval)) {
629 /* copy and check length */
630 if (strlcpy(docid, json_object_get_string(jval), sizeof(docid)) >= sizeof(docid)) {
631 RERROR("document id from row longer than MAX_KEY_SIZE (%d)", MAX_KEY_SIZE);
636 /* check for valid doc id */
638 RWARN("failed to fetch document id from row - skipping");
643 cb_error = couchbase_get_key(cb_inst, cookie, docid);
645 /* check error and object */
646 if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) {
648 RERROR("failed to execute get request or parse return");
650 rcode = RLM_MODULE_FAIL;
656 RDEBUG3("cookie->jobj == %s", json_object_to_json_string(cookie->jobj));
658 /* get element name for User-Name attribute */
659 if (mod_attribute_to_element("User-Name", inst->map, &element) == 0) {
660 /* get and check username element */
661 if (!json_object_object_get_ex(cookie->jobj, element, &jval)){
662 RDEBUG("cannot zap stale entry without username");
663 rcode = RLM_MODULE_FAIL;
666 /* copy json string value to user_name */
667 user_name = talloc_typed_strdup(request, json_object_get_string(jval));
669 RDEBUG("failed to find map entry for User-Name attribute");
670 rcode = RLM_MODULE_FAIL;
674 /* get element name for Acct-Session-Id attribute */
675 if (mod_attribute_to_element("Acct-Session-Id", inst->map, &element) == 0) {
676 /* get and check session id element */
677 if (!json_object_object_get_ex(cookie->jobj, element, &jval)){
678 RDEBUG("cannot zap stale entry without session id");
679 rcode = RLM_MODULE_FAIL;
682 /* copy json string value to session_id */
683 session_id = talloc_typed_strdup(request, json_object_get_string(jval));
685 RDEBUG("failed to find map entry for Acct-Session-Id attribute");
686 rcode = RLM_MODULE_FAIL;
690 /* get element name for NAS-IP-Address attribute */
691 if (mod_attribute_to_element("NAS-IP-Address", inst->map, &element) == 0) {
692 /* attempt to get and nas address element */
693 if (json_object_object_get_ex(cookie->jobj, element, &jval)){
694 nas_addr = inet_addr(json_object_get_string(jval));
698 /* get element name for NAS-Port attribute */
699 if (mod_attribute_to_element("NAS-Port", inst->map, &element) == 0) {
700 /* attempt to get nas port element */
701 if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
702 nas_port = (uint32_t) json_object_get_int(jval);
706 /* check terminal server */
707 int check = rad_check_ts(nas_addr, nas_port, user_name, session_id);
709 /* take action based on check return */
711 /* stale record - zap it if enabled */
712 if (inst->delete_stale_sessions) {
713 /* get element name for Framed-IP-Address attribute */
714 if (mod_attribute_to_element("Framed-IP-Address", inst->map, &element) == 0) {
715 /* attempt to get framed ip address element */
716 if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
717 framed_ip_addr = inet_addr(json_object_get_string(jval));
721 /* get element name for Framed-Port attribute */
722 if (mod_attribute_to_element("Framed-Port", inst->map, &element) == 0) {
723 /* attempt to get framed port element */
724 if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
725 if (strcmp(json_object_get_string(jval), "PPP") == 0) {
727 } else if (strcmp(json_object_get_string(jval), "SLIP") == 0) {
733 /* get element name for Acct-Session-Time attribute */
734 if (mod_attribute_to_element("Acct-Session-Time", inst->map, &element) == 0) {
735 /* attempt to get session time element */
736 if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
737 session_time = json_object_get_int(jval);
742 session_zap(request, nas_addr, nas_port, user_name, session_id,
743 framed_ip_addr, framed_proto, session_time);
745 } else if (check == 1) {
746 /* user is still logged in - increase count */
747 ++request->simul_count;
749 /* get element name for Framed-IP-Address attribute */
750 if (mod_attribute_to_element("Framed-IP-Address", inst->map, &element) == 0) {
751 /* attempt to get framed ip address element */
752 if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
753 framed_ip_addr = inet_addr(json_object_get_string(jval));
755 /* ensure 0 if not found */
760 /* get element name for Calling-Station-Id attribute */
761 if (mod_attribute_to_element("Calling-Station-Id", inst->map, &element) == 0) {
762 /* attempt to get framed ip address element */
763 if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
764 /* copy json string value to cs_id */
765 cs_id = talloc_typed_strdup(request, json_object_get_string(jval));
767 /* ensure null if not found */
772 /* Does it look like a MPP attempt? */
773 if (client_ip_addr && framed_ip_addr && framed_ip_addr == client_ip_addr) {
774 request->simul_mpp = 2;
775 } else if (client_cs_id && cs_id && !strncmp(cs_id, client_cs_id, 16)) {
776 request->simul_mpp = 2;
780 /* check failed - return error */
781 REDEBUG("failed to check the terminal server for user '%s'", user_name);
782 rcode = RLM_MODULE_FAIL;
786 /* free and reset document user name talloc */
787 if (user_name) TALLOC_FREE(user_name);
789 /* free and reset document calling station id talloc */
790 if (cs_id) TALLOC_FREE(cs_id);
792 /* free and reset document session id talloc */
793 if (session_id) TALLOC_FREE(session_id);
795 /* free and reset json object before fetching next row */
797 json_object_put(cookie->jobj);
803 RDEBUG("Retained %d open sessions for %s after verification",
804 request->simul_count, request->username->vp_strvalue);
807 if (user_name) talloc_free(user_name);
808 if (cs_id) talloc_free(cs_id);
809 if (session_id) talloc_free(session_id);
812 if (jrows) json_object_put(jrows);
814 /* free and reset json object */
816 json_object_put(cookie->jobj);
820 if (handle) fr_connection_release(inst->pool, handle);
823 * The Auth module apparently looks at request->simul_count,
824 * not the return value of this module when deciding to deny
825 * a call for too many sessions.
831 /** Detach the module
833 * Detach the module instance and free any allocated resources.
835 * @param instance The module instance.
836 * @return Returns 0 (success) in all conditions.
838 static int mod_detach(void *instance)
840 rlm_couchbase_t *inst = instance;
842 if (inst->map) json_object_put(inst->map);
843 if (inst->pool) fr_connection_pool_free(inst->pool);
849 * Hook into the FreeRADIUS module system.
851 extern module_t rlm_couchbase;
852 module_t rlm_couchbase = {
853 .magic = RLM_MODULE_INIT,
855 .type = RLM_TYPE_THREAD_SAFE,
856 .inst_size = sizeof(rlm_couchbase_t),
857 .config = module_config,
858 .instantiate = mod_instantiate,
859 .detach = mod_detach,
861 [MOD_AUTHORIZE] = mod_authorize,
862 #ifdef WITH_ACCOUNTING
863 [MOD_ACCOUNTING] = mod_accounting,
865 #ifdef WITH_SESSION_MGMT
866 [MOD_SESSION] = mod_checksimul