1 /*
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.
6  *
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.
11  *
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
15  */
16 
17 /**
18  * $Id: 1d4e024b8d037410be86696b4f3f972fab9068ad $
19  *
20  * @brief Utillity functions used in the module.
21  * @file mod.c
22  *
23  * @author Aaron Hurt <ahurt@anbcs.com>
24  * @copyright 2013-2014 The FreeRADIUS Server Project.
25  */
26 
27 RCSID("$Id: 1d4e024b8d037410be86696b4f3f972fab9068ad $")
28 
29 #include <freeradius-devel/radiusd.h>
30 
31 #include "mod.h"
32 #include "couchbase.h"
33 #include "jsonc_missing.h"
34 
35 /** Delete a conneciton pool handle and free related resources
36  *
37  * Destroys the underlying Couchbase connection handle freeing any related
38  * resources and closes the socket connection.
39  *
40  * @param  chandle The connection handle to destroy.
41  * @return         Always returns 0 (success) in all conditions.
42  */
_mod_conn_free(rlm_couchbase_handle_t * chandle)43 static int _mod_conn_free(rlm_couchbase_handle_t *chandle)
44 {
45 	lcb_t cb_inst = chandle->handle;                /* couchbase instance */
46 
47 	/* destroy/free couchbase instance */
48 	lcb_destroy(cb_inst);
49 
50 	/* return */
51 	return 0;
52 }
53 
54 /** Create a new connection pool handle
55  *
56  * Create a new connection to Couchbase within the pool and initialize
57  * information associated with the connection instance.
58  *
59  * @param  ctx      The connection parent context.
60  * @param  instance The module instance.
61  * @return          The new connection handle or NULL on error.
62  */
mod_conn_create(TALLOC_CTX * ctx,void * instance)63 void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
64 {
65 	rlm_couchbase_t *inst = instance;           /* module instance pointer */
66 	rlm_couchbase_handle_t *chandle = NULL;     /* connection handle pointer */
67 	cookie_t *cookie = NULL;                    /* couchbase cookie */
68 	lcb_t cb_inst;                              /* couchbase connection instance */
69 	lcb_error_t cb_error;			/* couchbase error status */
70 
71 	/* create instance */
72 	cb_error = couchbase_init_connection(&cb_inst, inst->server, inst->bucket, inst->password);
73 
74 	/* check couchbase instance */
75 	if (cb_error != LCB_SUCCESS) {
76 		ERROR("rlm_couchbase: failed to initiate couchbase connection: %s (0x%x)",
77 		      lcb_strerror(NULL, cb_error), cb_error);
78 		/* destroy/free couchbase instance */
79 		lcb_destroy(cb_inst);
80 		/* fail */
81 		return NULL;
82 	}
83 
84 	/* allocate memory for couchbase connection instance abstraction */
85 	chandle = talloc_zero(ctx, rlm_couchbase_handle_t);
86 	talloc_set_destructor(chandle, _mod_conn_free);
87 
88 	/* allocate cookie off handle */
89 	cookie = talloc_zero(chandle, cookie_t);
90 
91 	/* init tokener error and json object */
92 	cookie->jerr = json_tokener_success;
93 	cookie->jobj = NULL;
94 
95 	/* populate handle */
96 	chandle->cookie = cookie;
97 	chandle->handle = cb_inst;
98 
99 	/* return handle struct */
100 	return chandle;
101 }
102 
103 /** Build a JSON object map from the configuration "update" section
104  *
105  * Parse the "map" section from the module configuration file and store this
106  * as a JSON object (key/value list) in the module instance.  This map will be
107  * used to lookup and map attributes for all incoming accounting requests.
108  *
109  * @param  conf     Configuration section.
110  * @param  instance The module instance.
111  * @return          Returns 0 on success, -1 on error.
112  */
mod_build_attribute_element_map(CONF_SECTION * conf,void * instance)113 int mod_build_attribute_element_map(CONF_SECTION *conf, void *instance)
114 {
115 	rlm_couchbase_t *inst = instance;   /* our module instance */
116 	CONF_SECTION *cs;                   /* module config section */
117 	CONF_ITEM *ci;                      /* config item */
118 	CONF_PAIR *cp;                      /* conig pair */
119 	const char *attribute, *element;    /* attribute and element names */
120 
121 	/* find update section */
122 	cs = cf_section_sub_find(conf, "update");
123 
124 	/* backwards compatibility */
125 	if (!cs) {
126 		cs = cf_section_sub_find(conf, "map");
127 		WARN("rlm_couchbase: found deprecated 'map' section - please change to 'update'");
128 	}
129 
130 	/* check section */
131 	if (!cs) {
132 		ERROR("rlm_couchbase: failed to find 'update' section in config");
133 		/* fail */
134 		return -1;
135 	}
136 
137 	/* create attribute map object */
138 	inst->map = json_object_new_object();
139 
140 	/* parse update section */
141 	for (ci = cf_item_find_next(cs, NULL); ci != NULL; ci = cf_item_find_next(cs, ci)) {
142 		/* validate item */
143 		if (!cf_item_is_pair(ci)) {
144 			ERROR("rlm_couchbase: failed to parse invalid item in 'update' section");
145 			/* free map */
146 			if (inst->map) {
147 				json_object_put(inst->map);
148 			}
149 			/* fail */
150 			return -1;
151 		}
152 
153 		/* get value pair from item */
154 		cp = cf_item_to_pair(ci);
155 
156 		/* get pair name (attribute name) */
157 		attribute = cf_pair_attr(cp);
158 
159 		if (!dict_attrbyname(attribute)) {
160 			ERROR("Unknown RADIUS attribute '%s'", attribute);
161 			return -1;
162 		}
163 
164 		/* get pair value (element name) */
165 		element = cf_pair_value(cp);
166 
167 		/* add pair name and value */
168 		json_object_object_add(inst->map, attribute, json_object_new_string(element));
169 
170 		/* debugging */
171 		DEBUG3("rlm_couchbase: added attribute '%s' to element '%s' mapping", attribute, element);
172 	}
173 
174 	/* debugging */
175 	DEBUG3("rlm_couchbase: built attribute to element mapping %s", json_object_to_json_string(inst->map));
176 
177 	/* return */
178 	return 0;
179 }
180 
181 /** Map attributes to JSON element names
182  *
183  * Attempt to map the passed attribute name to the configured JSON element
184  * name using the JSON object map mod_build_attribute_element_map().
185  *
186  * @param  name The character name of the requested attribute.
187  * @param  map  The JSON object map to use for the lookup.
188  * @param  buf  The buffer where the given element will be stored if found.
189  * @return      Returns 0 on success, -1 on error.
190  */
mod_attribute_to_element(const char * name,json_object * map,void * buf)191 int mod_attribute_to_element(const char *name, json_object *map, void *buf)
192 {
193 	json_object *jval;  /* json object values */
194 
195 	/* clear buffer */
196 	memset((char *) buf, 0, MAX_KEY_SIZE);
197 
198 	/* attempt to map attribute */
199 	if (json_object_object_get_ex(map, name, &jval)) {
200 		/* copy and check size */
201 		if (strlcpy(buf, json_object_get_string(jval), MAX_KEY_SIZE) >= MAX_KEY_SIZE) {
202 			/* oops ... this value is bigger than our buffer ... error out */
203 			ERROR("rlm_couchbase: json map value larger than MAX_KEY_SIZE - %d", MAX_KEY_SIZE);
204 			/* return fail */
205 			return -1;
206 		}
207 		/* looks good */
208 		return 0;
209 	}
210 
211 	/* debugging */
212 	DEBUG("rlm_couchbase: skipping attribute with no map entry - %s", name);
213 
214 	/* default return */
215 	return -1;
216 }
217 
218 /** Build value pairs from the passed JSON object and add to the request
219  *
220  * Parse the passed JSON object and create value pairs that will be injected into
221  * the given request for authorization.
222  *
223  * Example JSON document structure:
224  * @code{.json}
225  * {
226  *   "docType": "raduser",
227  *   "userName": "test",
228  *   "config": {
229  *     "SHA-Password": {
230  *       "value": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
231  *       "op": ":="
232  *     }
233  *   },
234  *   "reply": {
235  *     "Reply-Message": {
236  *       "value": "Hidey Ho!",
237  *       "op": "="
238  *     }
239  *   }
240  * }
241  * @endcode
242  *
243  * @param  json    The JSON object representation of the user documnent.
244  * @param  section The pair section ("config" or "reply").
245  * @param  request The request to which the generated pairs should be added.
246  */
mod_json_object_to_value_pairs(json_object * json,const char * section,REQUEST * request)247 void *mod_json_object_to_value_pairs(json_object *json, const char *section, REQUEST *request)
248 {
249 	json_object *jobj, *jval, *jop;     /* json object pointers */
250 	TALLOC_CTX *ctx;                    /* talloc context for fr_pair_make */
251 	VALUE_PAIR *vp, **ptr;              /* value pair and value pair pointer for fr_pair_make */
252 
253 	/* assign ctx and vps for fr_pair_make based on section */
254 	if (strcmp(section, "config") == 0) {
255 		ctx = request;
256 		ptr = &(request->config);
257 	} else if (strcmp(section, "reply") == 0) {
258 		ctx = request->reply;
259 		ptr = &(request->reply->vps);
260 	} else {
261 		/* log error - this shouldn't happen */
262 		RERROR("invalid section passed for fr_pair_make");
263 		/* return */
264 		return NULL;
265 	}
266 
267 	/* get config payload */
268 	if (json_object_object_get_ex(json, section, &jobj)) {
269 		/* make sure we have the correct type */
270 		if ((jobj == NULL) || !json_object_is_type(jobj, json_type_object)) {
271 			/* log error */
272 			RERROR("invalid json type for '%s' section - sections must be json objects", section);
273 			/* reuturn */
274 			return NULL;
275 		}
276 		/* loop through object */
277 		json_object_object_foreach(jobj, attribute, json_vp) {
278 			/* check for appropriate type in value and op */
279 			if ((jobj == NULL) || !json_object_is_type(json_vp, json_type_object)) {
280 				/* log error */
281 				RERROR("invalid json type for '%s' attribute - attributes must be json objects",
282 				       attribute);
283 				/* return */
284 				return NULL;
285 			}
286 			/* debugging */
287 			RDEBUG("parsing '%s' attribute: %s => %s", section, attribute,
288 			       json_object_to_json_string(json_vp));
289 			/* create pair from json object */
290 			if (json_object_object_get_ex(json_vp, "value", &jval) &&
291 				json_object_object_get_ex(json_vp, "op", &jop)) {
292 				/* check for null before getting type */
293 				if (jval == NULL) return NULL;
294 				/* make correct pairs based on json object type */
295 				switch (json_object_get_type(jval)) {
296 				case json_type_double:
297 				case json_type_int:
298 				case json_type_string:
299 					/* debugging */
300 					RDEBUG("adding '%s' attribute to '%s' section", attribute, section);
301 					/* add pair */
302 					vp = fr_pair_make(ctx, ptr, attribute, json_object_get_string(jval),
303 						fr_str2int(fr_tokens, json_object_get_string(jop), 0));
304 					/* check pair */
305 					if (!vp) {
306 						RERROR("could not build value pair for '%s' attribute (%s)",
307 						       attribute, fr_strerror());
308 						/* return */
309 						return NULL;
310 					}
311 					break;
312 
313 				case json_type_object:
314 				case json_type_array:
315 					/* log error - we want to handle these eventually */
316 					RERROR("skipping unhandled nested json object or array value pair object");
317 					break;
318 
319 				default:
320 					/* log error - this shouldn't ever happen */
321 					RERROR("skipping unhandled json type in value pair object");
322 					break;
323 				}
324 			} else {
325 				/* log error */
326 				RERROR("failed to get 'value' or 'op' element for '%s' attribute", attribute);
327 			}
328 		}
329 		/* return NULL */
330 		return NULL;
331 	}
332 
333 	/* debugging */
334 	RDEBUG("couldn't find '%s' section in json object - not adding value pairs for this section", section);
335 
336 	/* return NULL */
337 	return NULL;
338 }
339 
340 /** Convert value pairs to json objects
341  *
342  * Take the passed value pair and convert it to a json-c JSON object.
343  * This code is heavily based on the vp_prints_value_json() function
344  * from src/lib/print.c.
345  *
346  * @param  request   The request object.
347  * @param  vp        The value pair to convert.
348  * @param  raw_value Print all values as raw, even if enum values exist.
349  * @return           Returns a JSON object.
350  */
mod_value_pair_to_json_object(REQUEST * request,VALUE_PAIR * vp,bool raw_value)351 json_object *mod_value_pair_to_json_object(REQUEST *request, VALUE_PAIR *vp, bool raw_value)
352 {
353 	char value[255];    /* radius attribute value */
354 
355 	/* add this attribute/value pair to our json output */
356 	if (!vp->da->flags.has_tag) {
357 		unsigned int i;
358 
359 		switch (vp->da->type) {
360 		case PW_TYPE_INTEGER:
361 			i = vp->vp_integer;
362 			goto print_int;
363 
364 		case PW_TYPE_SHORT:
365 			i = vp->vp_short;
366 			goto print_int;
367 
368 		case PW_TYPE_BYTE:
369 			i = vp->vp_byte;
370 
371 		print_int:
372 			/* add a raw value to our json output - i.e. do not try resolve enum.
373 			   skip this if raw_value is false, and we have a value in the dictionary */
374 			if (!raw_value && !vp->da->flags.has_value) break;
375 #ifdef HAVE_JSON_OBJECT_NEW_INT64
376 			/* debug */
377 			RDEBUG3("creating new int64 for unsigned 32 bit int/byte/short '%s'", vp->da->name);
378 			/* return as 64 bit int - JSON spec does not support unsigned ints */
379 			return json_object_new_int64(i);
380 #else
381 			/* debug */
382 			RDEBUG3("creating new int for unsigned 32 bit int/byte/short '%s'", vp->da->name);
383 			/* return as 64 bit int - JSON spec does not support unsigned ints */
384 			return json_object_new_int(i);
385 #endif
386 
387 		case PW_TYPE_SIGNED:
388 #ifdef HAVE_JSON_OBJECT_NEW_INT64
389 			/* debug */
390 			RDEBUG3("creating new int64 for signed 32 bit integer '%s'", vp->da->name);
391 			/* return as 64 bit int - json-c represents all ints as 64 bits internally */
392 			return json_object_new_int64(vp->vp_signed);
393 #else
394 			RDEBUG3("creating new int for signed 32 bit integer '%s'", vp->da->name);
395 			/* return as signed int */
396 			return json_object_new_int(vp->vp_signed);
397 #endif
398 
399 		case PW_TYPE_INTEGER64:
400 #ifdef HAVE_JSON_OBJECT_NEW_INT64
401 			/* debug */
402 			RDEBUG3("creating new int64 for 64 bit integer '%s'", vp->da->name);
403 			/* return as 64 bit int - because it is a 64 bit int */
404 			return json_object_new_int64(vp->vp_integer64);
405 #else
406 			/* warning */
407 			RWARN("skipping 64 bit integer attribute '%s' - please upgrade json-c to 0.10+", vp->da->name);
408 			break;
409 #endif
410 
411 		default:
412 			/* silence warnings - do nothing */
413 		break;
414 		}
415 	}
416 
417 	/* keep going if not set above */
418 	switch (vp->da->type) {
419 	case PW_TYPE_STRING:
420 		/* debug */
421 		RDEBUG3("assigning string '%s' as string", vp->da->name);
422 		/* return string value */
423 		return json_object_new_string(vp->vp_strvalue);
424 
425 	default:
426 		/* debug */
427 		RDEBUG3("assigning unhandled '%s' as string", vp->da->name);
428 		/* get standard value */
429 		vp_prints_value(value, sizeof(value), vp, 0);
430 		/* return string value from above */
431 		return json_object_new_string(value);
432 	}
433 }
434 
435 /** Ensure accounting documents always contain a valid timestamp
436  *
437  * Inspect the given JSON object representation of an accounting document
438  * fetched from Couchbase and ensuse it contains a valid (non NULL) timestamp value.
439  *
440  * @param  json JSON object representation of an accounting document.
441  * @param  vps  The value pairs associated with the current accounting request.
442  * @return      Returns 0 on success, -1 on error.
443  */
mod_ensure_start_timestamp(json_object * json,VALUE_PAIR * vps)444 int mod_ensure_start_timestamp(json_object *json, VALUE_PAIR *vps)
445 {
446 	json_object *jval;      /* json object value */
447 	struct tm tm;           /* struct to hold event time */
448 	time_t ts = 0;          /* values to hold time in seconds */
449 	VALUE_PAIR *vp;         /* values to hold value pairs */
450 	char value[255];        /* store radius attribute values and our timestamp */
451 
452 	/* get our current start timestamp from our json body */
453 	if (json_object_object_get_ex(json, "startTimestamp", &jval) == 0) {
454 		/* debugging ... this shouldn't ever happen */
455 		DEBUG("rlm_couchbase: failed to find 'startTimestamp' in current json body");
456 		/* return */
457 		return -1;
458 	}
459 
460 	/* check for null value */
461 	if (json_object_get_string(jval) != NULL) {
462 		/* already set - nothing left to do */
463 		return 0;
464 	}
465 
466 	/* get current event timestamp */
467 	if ((vp = fr_pair_find_by_num(vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) {
468 		/* get seconds value from attribute */
469 		ts = vp->vp_date;
470 	} else {
471 		/* debugging */
472 		DEBUG("rlm_couchbase: failed to find event timestamp in current request");
473 		/* return */
474 		return -1;
475 	}
476 
477 	/* clear value */
478 	memset(value, 0, sizeof(value));
479 
480 	/* get elapsed session time */
481 	if ((vp = fr_pair_find_by_num(vps, PW_ACCT_SESSION_TIME, 0, TAG_ANY)) != NULL) {
482 		/* calculate diff */
483 		ts = (ts - vp->vp_integer);
484 		/* calculate start time */
485 		size_t length = strftime(value, sizeof(value), "%b %e %Y %H:%M:%S %Z", localtime_r(&ts, &tm));
486 		/* check length */
487 		if (length > 0) {
488 			/* debugging */
489 			DEBUG("rlm_couchbase: calculated start timestamp: %s", value);
490 			/* store new value in json body */
491 			json_object_object_add(json, "startTimestamp", json_object_new_string(value));
492 		} else {
493 			/* debugging */
494 			DEBUG("rlm_couchbase: failed to format calculated timestamp");
495 			/* return */
496 			return -1;
497 		}
498 	}
499 
500 	/* default return */
501 	return 0;
502 }
503 
504 /** Handle client value processing for client_map_section()
505  *
506  * @param  out  Character output
507  * @param  cp   Configuration pair
508  * @param  data The client data
509  * @return      Returns 0 on success, -1 on error.
510  */
_get_client_value(char ** out,CONF_PAIR const * cp,void * data)511 static int _get_client_value(char **out, CONF_PAIR const *cp, void *data)
512 {
513 	json_object *jval;
514 
515 	if (!json_object_object_get_ex((json_object *)data, cf_pair_value(cp), &jval)) {
516 		*out = NULL;
517 		return 0;
518 	}
519 
520 	if (!jval) return -1;
521 
522 	*out = talloc_strdup(NULL, json_object_get_string(jval));
523 	if (!*out) return -1;
524 
525 	return 0;
526 }
527 
528 /** Load client entries from Couchbase client documents on startup
529  *
530  * This function executes the view defined in the module configuration and loops
531  * through all returned rows.  The view is called with "stale=false" to ensure the
532  * most accurate data available when the view is called.  This will force an index
533  * rebuild on this design document in Couchbase.  However, since this function is only
534  * run once at sever startup this should not be a concern.
535  *
536  * @param  inst The module instance.
537  * @param  tmpl Default values for new clients.
538  * @param  map  The client attribute configuration section.
539  * @return      Returns 0 on success, -1 on error.
540  */
mod_load_client_documents(rlm_couchbase_t * inst,CONF_SECTION * tmpl,CONF_SECTION * map)541 int mod_load_client_documents(rlm_couchbase_t *inst, CONF_SECTION *tmpl, CONF_SECTION *map)
542 {
543 	rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */
544 	char vpath[256], vid[MAX_KEY_SIZE], vkey[MAX_KEY_SIZE];  /* view path and fields */
545 	char error[512];                                         /* view error return */
546 	size_t idx = 0;                                          /* row array index counter */
547 	int retval = 0;                                          /* return value */
548 	lcb_error_t cb_error = LCB_SUCCESS;                      /* couchbase error holder */
549 	json_object *json, *jval;                                /* json object holders */
550 	json_object *jrows = NULL;                               /* json object to hold view rows */
551 	CONF_SECTION *client;                                    /* freeradius config section */
552 	RADCLIENT *c;                                            /* freeradius client */
553 	int slen;
554 
555 	/* get handle */
556 	handle = fr_connection_get(inst->pool);
557 
558 	/* check handle */
559 	if (!handle) return -1;
560 
561 	/* set couchbase instance */
562 	lcb_t cb_inst = handle->handle;
563 
564 	/* set cookie */
565 	cookie_t *cookie = handle->cookie;
566 
567 	/* build view path */
568 	slen = snprintf(vpath, sizeof(vpath), "%s?stale=false", inst->client_view);
569 	if (slen >= (int) sizeof(vpath) || slen < 0) {
570 		ERROR("rlm_couchbase: view path too long");
571 		retval=-1;
572 		goto free_and_return;
573 	}
574 
575 
576 	/* query view for document */
577 	cb_error = couchbase_query_view(cb_inst, cookie, vpath, NULL);
578 
579 	/* check error and object */
580 	if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) {
581 		/* log error */
582 		ERROR("rlm_couchbase: failed to execute view request or parse return");
583 		/* set return */
584 		retval = -1;
585 		/* return */
586 		goto free_and_return;
587 	}
588 
589 	/* debugging */
590 	DEBUG3("rlm_couchbase: cookie->jobj == %s", json_object_to_json_string(cookie->jobj));
591 
592 	/* check for error in json object */
593 	if (json_object_object_get_ex(cookie->jobj, "error", &json)) {
594 		/* build initial error buffer */
595 		strlcpy(error, json_object_get_string(json), sizeof(error));
596 		/* get error reason */
597 		if (json_object_object_get_ex(cookie->jobj, "reason", &json)) {
598 			/* append divider */
599 			strlcat(error, " - ", sizeof(error));
600 			/* append reason */
601 			strlcat(error, json_object_get_string(json), sizeof(error));
602 		}
603 		/* log error */
604 		ERROR("rlm_couchbase: view request failed with error: %s", error);
605 		/* set return */
606 		retval = -1;
607 		/* return */
608 		goto free_and_return;
609 	}
610 
611 	/* check for document id in return */
612 	if (!json_object_object_get_ex(cookie->jobj, "rows", &json)) {
613 		/* log error */
614 		ERROR("rlm_couchbase: failed to fetch rows from view payload");
615 		/* set return */
616 		retval = -1;
617 		/* return */
618 		goto free_and_return;
619 	}
620 
621 	/* get and hold rows */
622 	jrows = json_object_get(json);
623 
624 	/* free cookie object */
625 	if (cookie->jobj) {
626 		json_object_put(cookie->jobj);
627 		cookie->jobj = NULL;
628 	}
629 
630 	/* debugging */
631 	DEBUG3("rlm_couchbase: jrows == %s", json_object_to_json_string(jrows));
632 
633 	/* check for valid row value */
634 	if ((jrows == NULL) || !json_object_is_type(jrows, json_type_array) || json_object_array_length(jrows) < 1) {
635 		/* log error */
636 		ERROR("rlm_couchbase: no valid rows returned from view: %s", vpath);
637 		/* set return */
638 		retval = -1;
639 		/* return */
640 		goto free_and_return;
641 	}
642 
643 	/* loop across all row elements */
644 	for (idx = 0; idx < (size_t)json_object_array_length(jrows); idx++) {
645 		/* fetch current index */
646 		json = json_object_array_get_idx(jrows, idx);
647 
648 		/* get view id */
649 		if (json_object_object_get_ex(json, "id", &jval)) {
650 			/* clear view id */
651 			memset(vid, 0, sizeof(vid));
652 			/* copy and check length */
653 			if (strlcpy(vid, json_object_get_string(jval), sizeof(vid)) >= sizeof(vid)) {
654 				ERROR("rlm_couchbase: id from row longer than MAX_KEY_SIZE (%d)",
655 				      MAX_KEY_SIZE);
656 				continue;
657 			}
658 		} else {
659 			WARN("rlm_couchbase: failed to fetch id from row - skipping");
660 			continue;
661 		}
662 
663 		/* get view key */
664 		if (json_object_object_get_ex(json, "key", &jval)) {
665 			/* clear view key */
666 			memset(vkey, 0, sizeof(vkey));
667 			/* copy and check length */
668 			if (strlcpy(vkey, json_object_get_string(jval), sizeof(vkey)) >= sizeof(vkey)) {
669 				ERROR("rlm_couchbase: key from row longer than MAX_KEY_SIZE (%d)",
670 				      MAX_KEY_SIZE);
671 				continue;
672 			}
673 		} else {
674 			WARN("rlm_couchbase: failed to fetch key from row - skipping");
675 			continue;
676 		}
677 
678 		/* fetch document */
679 		cb_error = couchbase_get_key(cb_inst, cookie, vid);
680 
681 		/* check error and object */
682 		if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) {
683 			/* log error */
684 			ERROR("rlm_couchbase: failed to execute get request or parse return");
685 			/* set return */
686 			retval = -1;
687 			/* return */
688 			goto free_and_return;
689 		}
690 
691 		/* debugging */
692 		DEBUG3("rlm_couchbase: cookie->jobj == %s", json_object_to_json_string(cookie->jobj));
693 
694 		/* allocate conf section */
695 		client = tmpl ? cf_section_dup(NULL, tmpl, "client", vkey, true) :
696 				cf_section_alloc(NULL, "client", vkey);
697 
698 		if (client_map_section(client, map, _get_client_value, cookie->jobj) < 0) {
699 			/* free config setion */
700 			talloc_free(client);
701 			/* set return */
702 			retval = -1;
703 			/* return */
704 			goto free_and_return;
705 		}
706 
707 		/*
708 		 * @todo These should be parented from something.
709 		 */
710 		c = client_afrom_cs(NULL, client, false, false);
711 		if (!c) {
712 			ERROR("rlm_couchbase: failed to allocate client");
713 			/* free config setion */
714 			talloc_free(client);
715 			/* set return */
716 			retval = -1;
717 			/* return */
718 			goto free_and_return;
719 		}
720 
721 		/*
722 		 * Client parents the CONF_SECTION which defined it.
723 		 */
724 		talloc_steal(c, client);
725 
726 		/* attempt to add client */
727 		if (!client_add(NULL, c)) {
728 			ERROR("rlm_couchbase: failed to add client '%s' from '%s', possible duplicate?", vkey, vid);
729 			/* free client */
730 			client_free(c);
731 			/* set return */
732 			retval = -1;
733 			/* return */
734 			goto free_and_return;
735 		}
736 
737 		/* debugging */
738 		DEBUG("rlm_couchbase: client '%s' added", c->longname);
739 
740 		/* free json object */
741 		if (cookie->jobj) {
742 			json_object_put(cookie->jobj);
743 			cookie->jobj = NULL;
744 		}
745 	}
746 
747 	free_and_return:
748 
749 	/* free rows */
750 	if (jrows) {
751 		json_object_put(jrows);
752 	}
753 
754 	/* free json object */
755 	if (cookie->jobj) {
756 		json_object_put(cookie->jobj);
757 		cookie->jobj = NULL;
758 	}
759 
760 	/* release handle */
761 	if (handle) {
762 		fr_connection_release(inst->pool, handle);
763 	}
764 
765 	/* return */
766 	return retval;
767 }
768