コード例 #1
0
ファイル: jpath.c プロジェクト: FreeRADIUS/freeradius-server
/** Recursive function for jpath_expr_evaluate
 *
 * @param[in,out] ctx to allocate fr_value_box_t in.
 * @param[out] tail Where to write fr_value_box_t (**).
 * @param[in] dst_type FreeRADIUS type to convert to.
 * @param[in] dst_enumv Enumeration values to allow string to integer conversions.
 * @param[in] object current node in the json tree.
 * @param[in] jpath to evaluate.
 * @return
 *	- 1 on match.
 *	- 0 on no match.
 *	- -1 on error.
 */
static int jpath_evaluate(TALLOC_CTX *ctx, fr_value_box_t ***tail,
			  fr_type_t dst_type, fr_dict_attr_t const *dst_enumv,
			  json_object *object, fr_jpath_node_t const *jpath)
{
	fr_value_box_t		*value;
	fr_jpath_node_t const	*node;
	jpath_selector_t const	*selector;
	bool			child_matched = false;
	int			ret = 0;

	/*
	 *	Iterate over the nodes, we only recurse for
	 *	more complex operations.
	 */
	for (node = jpath; node; node = node->next) switch (node->selector->type) {
	case JPATH_SELECTOR_FIELD:
		if (!fr_json_object_is_type(object, json_type_object)) return 0;
		if (!json_object_object_get_ex(object, node->selector->field, &object)) return 0;
		continue;

	case JPATH_SELECTOR_INDEX:
	case JPATH_SELECTOR_SLICE:
	/*
	 *	There may be multiple selectors per node
	 */
	for (selector = node->selector; selector; selector = selector->next) switch (selector->type) {
		case JPATH_SELECTOR_INDEX:
		{
			struct array_list *array_obj;	/* Because array_list is a global... */

			rad_assert(selector->slice[0] != SELECTOR_INDEX_UNSET);

			if (!fr_json_object_is_type(object, json_type_array)) return 0;
			array_obj = json_object_get_array(object);
			if ((selector->slice[0] < 0) ||
			    (selector->slice[0] >= (int32_t)(array_obj->length & INT32_MAX))) continue;

			ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
					     array_obj->array[selector->slice[0]], node->next);
			if (ret < 0) return ret;
			if (ret == 1) child_matched = true;
		}
			break;

		case JPATH_SELECTOR_SLICE:
		{
			struct array_list *array_obj;
			int32_t start, end, step, i;

			if (!fr_json_object_is_type(object, json_type_array)) return 0;
			array_obj = json_object_get_array(object);

			/*
			 *	This logic may seem slightly odd, but it perfectly
			 *	emulates python array slicing behaviour AFAICT
			 */
			step = selector->slice[2];
			if (step == SELECTOR_INDEX_UNSET) step = 1;

			start = selector->slice[0];
			if (start == SELECTOR_INDEX_UNSET) start = (step < 0) ?
				(int32_t)((array_obj->length - 1) & INT32_MAX) : 0;
			else if (start < 0) start = array_obj->length + start;

			end = selector->slice[1];
			if (end == SELECTOR_INDEX_UNSET) end = (step < 0) ?
				-1 : (int32_t)((array_obj->length - 1) & INT32_MAX);
			else if (end < 0) end = array_obj->length + end;

			/*
			 *	Descending
			 */
			if (step < 0) for (i = start; (i > end) && (i >= 0); i += step) {
				rad_assert((i >= 0) && (i < (int32_t)(array_obj->length & INT32_MAX)));
				ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
						     array_obj->array[i], node->next);
				if (ret < 0) return ret;
				if (ret == 1) child_matched = true;
			/*
			 *	Ascending
			 */
			} else for (i = start; (i < end) && (i < (int32_t)(array_obj->length & INT32_MAX)); i += step) {
				rad_assert((i >= 0) && (i < (int32_t)(array_obj->length & INT32_MAX)));
				ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
						     array_obj->array[i], node->next);
				if (ret < 0) return ret;
				if (ret == 1) child_matched = true;
			}
		}
			break;

		default:
			rad_assert(0);
			return -1;
	}
		return child_matched ? 1 : 0;

	/*
	 *	Iterate over fields or array indices
	 */
	case JPATH_SELECTOR_WILDCARD:
	{
		int i;

		if (fr_json_object_is_type(object, json_type_array)) {
			struct array_list *array_obj;

			array_obj = json_object_get_array(object);
			for (i = 0; i < (int32_t)(array_obj->length & INT32_MAX); i++) {
				ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
						     array_obj->array[i], node->next);
				if (ret < 0) return ret;
				if (ret == 1) child_matched = true;
			}
			return child_matched ? 1 : 0;
		} else if (fr_json_object_is_type(object, json_type_object)) {
			json_object_object_foreach(object, field_name, field_value) {
#ifndef NDEBUG
				rad_assert(field_name);
#else
				UNUSED_VAR(field_name);
#endif
				ret = jpath_evaluate(ctx, tail, dst_type, dst_enumv,
						     field_value, node->next);
				if (ret < 0) return ret;
				if (ret == 1) child_matched = true;
			}
			return child_matched ? 1 : 0;
		} else return 0;
	}
コード例 #2
0
ファイル: mod.c プロジェクト: peruchi/freeradius-server
/** Build value pairs from the passed JSON object and add to the request
 *
 * Parse the passed JSON object and create value pairs that will be injected into
 * the given request for authorization.
 *
 * Example JSON document structure:
 * @code{.json}
 * {
 *   "docType": "raduser",
 *   "userName": "******",
 *   "config": {
 *     "SHA-Password": {
 *       "value": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
 *       "op": ":="
 *     }
 *   },
 *   "reply": {
 *     "Reply-Message": {
 *       "value": "Hidey Ho!",
 *       "op": "="
 *     }
 *   }
 * }
 * @endcode
 *
 * @param  json    The JSON object representation of the user documnent.
 * @param  section The pair section ("config" or "reply").
 * @param  request The request to which the generated pairs should be added.
 */
void *mod_json_object_to_value_pairs(json_object *json, const char *section, REQUEST *request)
{
	json_object *jobj, *jval, *jop;     /* json object pointers */
	TALLOC_CTX *ctx;                    /* talloc context for fr_pair_make */
	VALUE_PAIR *vp, **ptr;              /* value pair and value pair pointer for fr_pair_make */

	/* assign ctx and vps for fr_pair_make based on section */
	if (strcmp(section, "config") == 0) {
		ctx = request;
		ptr = &(request->config);
	} else if (strcmp(section, "reply") == 0) {
		ctx = request->reply;
		ptr = &(request->reply->vps);
	} else {
		/* log error - this shouldn't happen */
		RERROR("invalid section passed for fr_pair_make");
		/* return */
		return NULL;
	}

	/* get config payload */
	if (json_object_object_get_ex(json, section, &jobj)) {
		/* make sure we have the correct type */
		if (!fr_json_object_is_type(jobj, json_type_object)) {
			/* log error */
			RERROR("invalid json type for '%s' section - sections must be json objects", section);
			/* reuturn */
			return NULL;
		}
		/* loop through object */
		json_object_object_foreach(jobj, attribute, json_vp) {
			/* check for appropriate type in value and op */
			if (!fr_json_object_is_type(json_vp, json_type_object)) {
				/* log error */
				RERROR("invalid json type for '%s' attribute - attributes must be json objects",
				       attribute);
				/* return */
				return NULL;
			}
			/* debugging */
			RDEBUG("parsing '%s' attribute: %s => %s", section, attribute,
			       json_object_to_json_string(json_vp));
			/* create pair from json object */
			if (json_object_object_get_ex(json_vp, "value", &jval) &&
				json_object_object_get_ex(json_vp, "op", &jop)) {
				/* make correct pairs based on json object type */
				switch (fr_json_object_get_type(jval)) {
				case json_type_double:
				case json_type_int:
				case json_type_string:
					/* debugging */
					RDEBUG("adding '%s' attribute to '%s' section", attribute, section);
					/* add pair */
					vp = fr_pair_make(ctx, ptr, attribute, json_object_get_string(jval),
						fr_str2int(fr_tokens, json_object_get_string(jop), 0));
					/* check pair */
					if (!vp) {
						RERROR("could not build value pair for '%s' attribute (%s)",
						       attribute, fr_strerror());
						/* return */
						return NULL;
					}
					break;

				case json_type_object:
				case json_type_array:
					/* log error - we want to handle these eventually */
					RERROR("skipping unhandled nested json object or array value pair object");
					break;

				default:
					/* log error - this shouldn't ever happen */
					RERROR("skipping unhandled json type in value pair object");
					break;
				}
			} else {
				/* log error */
				RERROR("failed to get 'value' or 'op' element for '%s' attribute", attribute);
			}
		}
		/* return NULL */
		return NULL;
	}

	/* debugging */
	RDEBUG("couldn't find '%s' section in json object - not adding value pairs for this section", section);

	/* return NULL */
	return NULL;
}
コード例 #3
0
ファイル: mod.c プロジェクト: peruchi/freeradius-server
/** Load client entries from Couchbase client documents on startup
 *
 * This function executes the view defined in the module configuration and loops
 * through all returned rows.  The view is called with "stale=false" to ensure the
 * most accurate data available when the view is called.  This will force an index
 * rebuild on this design document in Couchbase.  However, since this function is only
 * run once at sever startup this should not be a concern.
 *
 * @param  inst The module instance.
 * @param  tmpl Default values for new clients.
 * @param  map  The client attribute configuration section.
 * @return
 *	- 0 on success.
 *	- -1 on failure.
 */
int mod_load_client_documents(rlm_couchbase_t *inst, CONF_SECTION *tmpl, CONF_SECTION *map)
{
	rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */
	char vpath[256], vid[MAX_KEY_SIZE], vkey[MAX_KEY_SIZE];  /* view path and fields */
	char error[512];                                         /* view error return */
	int idx = 0;                                             /* row array index counter */
	int retval = 0;                                          /* return value */
	lcb_error_t cb_error = LCB_SUCCESS;                      /* couchbase error holder */
	json_object *json, *jval;                                /* json object holders */
	json_object *jrows = NULL;                               /* json object to hold view rows */
	CONF_SECTION *client;                                    /* freeradius config section */
	RADCLIENT *c;                                            /* freeradius client */

	/* get handle */
	handle = fr_connection_get(inst->pool);

	/* check handle */
	if (!handle) return -1;

	/* set couchbase instance */
	lcb_t cb_inst = handle->handle;

	/* set cookie */
	cookie_t *cookie = handle->cookie;

	/* build view path */
	snprintf(vpath, sizeof(vpath), "%s?stale=false", inst->client_view);

	/* query view for document */
	cb_error = couchbase_query_view(cb_inst, cookie, vpath, NULL);

	/* check error and object */
	if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) {
		/* log error */
		ERROR("rlm_couchbase: failed to execute view request or parse return");
		/* set return */
		retval = -1;
		/* return */
		goto free_and_return;
	}

	/* debugging */
	DEBUG3("rlm_couchbase: cookie->jobj == %s", json_object_to_json_string(cookie->jobj));

	/* check for error in json object */
	if (json_object_object_get_ex(cookie->jobj, "error", &json)) {
		/* build initial error buffer */
		strlcpy(error, json_object_get_string(json), sizeof(error));
		/* get error reason */
		if (json_object_object_get_ex(cookie->jobj, "reason", &json)) {
			/* append divider */
			strlcat(error, " - ", sizeof(error));
			/* append reason */
			strlcat(error, json_object_get_string(json), sizeof(error));
		}
		/* log error */
		ERROR("rlm_couchbase: view request failed with error: %s", error);
		/* set return */
		retval = -1;
		/* return */
		goto free_and_return;
	}

	/* check for document id in return */
	if (!json_object_object_get_ex(cookie->jobj, "rows", &json)) {
		/* log error */
		ERROR("rlm_couchbase: failed to fetch rows from view payload");
		/* set return */
		retval = -1;
		/* return */
		goto free_and_return;
	}

	/* get and hold rows */
	jrows = json_object_get(json);

	/* free cookie object */
	if (cookie->jobj) {
		json_object_put(cookie->jobj);
		cookie->jobj = NULL;
	}

	/* debugging */
	DEBUG3("rlm_couchbase: jrows == %s", json_object_to_json_string(jrows));

	/* check for valid row value */
	if (!fr_json_object_is_type(jrows, json_type_array) || json_object_array_length(jrows) < 1) {
		/* log error */
		ERROR("rlm_couchbase: no valid rows returned from view: %s", vpath);
		/* set return */
		retval = -1;
		/* return */
		goto free_and_return;
	}

	/* loop across all row elements */
	for (idx = 0; idx < json_object_array_length(jrows); idx++) {
		/* fetch current index */
		json = json_object_array_get_idx(jrows, idx);

		/* get view id */
		if (json_object_object_get_ex(json, "id", &jval)) {
			/* clear view id */
			memset(vid, 0, sizeof(vid));
			/* copy and check length */
			if (strlcpy(vid, json_object_get_string(jval), sizeof(vid)) >= sizeof(vid)) {
				ERROR("rlm_couchbase: id from row longer than MAX_KEY_SIZE (%d)",
				      MAX_KEY_SIZE);
				continue;
			}
		} else {
			WARN("rlm_couchbase: failed to fetch id from row - skipping");
			continue;
		}

		/* get view key */
		if (json_object_object_get_ex(json, "key", &jval)) {
			/* clear view key */
			memset(vkey, 0, sizeof(vkey));
			/* copy and check length */
			if (strlcpy(vkey, json_object_get_string(jval), sizeof(vkey)) >= sizeof(vkey)) {
				ERROR("rlm_couchbase: key from row longer than MAX_KEY_SIZE (%d)",
				      MAX_KEY_SIZE);
				continue;
			}
		} else {
			WARN("rlm_couchbase: failed to fetch key from row - skipping");
			continue;
		}

		/* fetch document */
		cb_error = couchbase_get_key(cb_inst, cookie, vid);

		/* check error and object */
		if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) {
			/* log error */
			ERROR("rlm_couchbase: failed to execute get request or parse return");
			/* set return */
			retval = -1;
			/* return */
			goto free_and_return;
		}

		/* debugging */
		DEBUG3("rlm_couchbase: cookie->jobj == %s", json_object_to_json_string(cookie->jobj));

		/* allocate conf section */
		client = tmpl ? cf_section_dup(NULL, tmpl, "client", vkey, true) :
				cf_section_alloc(NULL, "client", vkey);

		if (client_map_section(client, map, _get_client_value, cookie->jobj) < 0) {
			/* free config setion */
			talloc_free(client);
			/* set return */
			retval = -1;
			/* return */
			goto free_and_return;
		}

		/*
		 * @todo These should be parented from something.
		 */
		c = client_afrom_cs(NULL, client, false, false);
		if (!c) {
			ERROR("rlm_couchbase: failed to allocate client");
			/* free config setion */
			talloc_free(client);
			/* set return */
			retval = -1;
			/* return */
			goto free_and_return;
		}

		/*
		 * Client parents the CONF_SECTION which defined it.
		 */
		talloc_steal(c, client);

		/* attempt to add client */
		if (!client_add(NULL, c)) {
			ERROR("rlm_couchbase: failed to add client '%s' from '%s', possible duplicate?", vkey, vid);
			/* free client */
			client_free(c);
			/* set return */
			retval = -1;
			/* return */
			goto free_and_return;
		}

		/* debugging */
		DEBUG("rlm_couchbase: client '%s' added", c->longname);

		/* free json object */
		if (cookie->jobj) {
			json_object_put(cookie->jobj);
			cookie->jobj = NULL;
		}
	}

	free_and_return:

	/* free rows */
	if (jrows) {
		json_object_put(jrows);
	}

	/* free json object */
	if (cookie->jobj) {
		json_object_put(cookie->jobj);
		cookie->jobj = NULL;
	}

	/* release handle */
	if (handle) {
		fr_connection_release(inst->pool, handle);
	}

	/* return */
	return retval;
}
コード例 #4
0
/** Check if a given user is already logged in.
 *
 * Process accounting data to determine if a user is already logged in. Sets request->simul_count
 * to the current session count for this user.
 *
 * Check twice. If on the first pass the user exceeds his maximum number of logins, do a second
 * pass and validate all logins by querying the terminal server.
 *
 * @param instance The module instance.
 * @param request  The checksimul request object.
 * @return Operation status (#rlm_rcode_t).
 */
static rlm_rcode_t mod_checksimul(void *instance, REQUEST *request) {
	rlm_couchbase_t *inst = instance;      /* our module instance */
	rlm_rcode_t rcode = RLM_MODULE_OK;     /* return code */
	rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */
	char vpath[256];

	char buffer[MAX_KEY_SIZE];
	char const *vkey;                      /* view path and query key */
	char docid[MAX_KEY_SIZE];              /* document id returned from view */
	char error[512];                       /* view error return */
	int idx = 0;                           /* row array index counter */
	char element[MAX_KEY_SIZE];            /* mapped radius attribute to element name */
	lcb_error_t cb_error = LCB_SUCCESS;    /* couchbase error holder */
	json_object *json, *jval;              /* json object holders */
	json_object *jrows = NULL;             /* json object to hold view rows */
	VALUE_PAIR *vp;                        /* value pair */
	uint32_t client_ip_addr = 0;           /* current client ip address */
	char const *client_cs_id = NULL;       /* current client calling station id */
	char *user_name = NULL;                /* user name from accounting document */
	char *session_id = NULL;               /* session id from accounting document */
	char *cs_id = NULL;                    /* calling station id from accounting document */
	uint32_t nas_addr = 0;                 /* nas address from accounting document */
	uint32_t nas_port = 0;                 /* nas port from accounting document */
	uint32_t framed_ip_addr = 0;           /* framed ip address from accounting document */
	char framed_proto = 0;                 /* framed proto from accounting document */
	int session_time = 0;                  /* session time from accounting document */
	ssize_t slen;

	/* do nothing if this is not enabled */
	if (inst->check_simul != true) {
		RDEBUG3("mod_checksimul returning noop - not enabled");
		return RLM_MODULE_NOOP;
	}

	/* ensure valid username in request */
	if ((!request->username) || (request->username->vp_length == '\0')) {
		RDEBUG3("mod_checksimul - invalid username");
		return RLM_MODULE_INVALID;
	}

	slen = tmpl_expand(&vkey, buffer, sizeof(buffer), request, inst->simul_vkey, NULL, NULL);
	if (slen < 0) return RLM_MODULE_FAIL;
	if ((vkey == buffer) && is_truncated((size_t)slen, sizeof(buffer))) {
		REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen);
		return RLM_MODULE_FAIL;
	}

	/* get handle */
	handle = fr_connection_get(inst->pool);

	/* check handle */
	if (!handle) return RLM_MODULE_FAIL;

	/* set couchbase instance */
	lcb_t cb_inst = handle->handle;

	/* set cookie */
	cookie_t *cookie = handle->cookie;

	/* build view path */
	snprintf(vpath, sizeof(vpath), "%s?key=\"%s\"&stale=update_after",
		 inst->simul_view, vkey);

	/* query view for document */
	cb_error = couchbase_query_view(cb_inst, cookie, vpath, NULL);

	/* check error and object */
	if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) {
		/* log error */
		RERROR("failed to execute view request or parse return");
		/* set return */
		rcode = RLM_MODULE_FAIL;
		/* return */
		goto finish;
	}

	/* debugging */
	RDEBUG3("cookie->jobj == %s", json_object_to_json_string(cookie->jobj));

	/* check for error in json object */
	if (json_object_object_get_ex(cookie->jobj, "error", &json)) {
		/* build initial error buffer */
		strlcpy(error, json_object_get_string(json), sizeof(error));
		/* get error reason */
		if (json_object_object_get_ex(cookie->jobj, "reason", &json)) {
			/* append divider */
			strlcat(error, " - ", sizeof(error));
			/* append reason */
			strlcat(error, json_object_get_string(json), sizeof(error));
		}
		/* log error */
		RERROR("view request failed with error: %s", error);
		/* set return */
		rcode = RLM_MODULE_FAIL;
		/* return */
		goto finish;
	}

	/* check for document id in return */
	if (!json_object_object_get_ex(cookie->jobj, "rows", &json)) {
		/* log error */
		RERROR("failed to fetch rows from view payload");
		/* set return */
		rcode = RLM_MODULE_FAIL;
		/* return */
		goto finish;
	}

	/* get and hold rows */
	jrows = json_object_get(json);

	/* free cookie object */
	if (cookie->jobj) {
		json_object_put(cookie->jobj);
		cookie->jobj = NULL;
	}

	/* check for valid row value */
	if (!jrows || !fr_json_object_is_type(jrows, json_type_array)) {
		/* log error */
		RERROR("no valid rows returned from view: %s", vpath);
		/* set return */
		rcode = RLM_MODULE_FAIL;
		/* return */
		goto finish;
	}

	/* debugging */
	RDEBUG3("jrows == %s", json_object_to_json_string(jrows));

	/* set the count */
	request->simul_count = json_object_array_length(jrows);

	/* debugging */
	RDEBUG("found %d open sessions for %s", request->simul_count, request->username->vp_strvalue);

	/* check count */
	if (request->simul_count < request->simul_max) {
		rcode = RLM_MODULE_OK;
		goto finish;
	}

	/*
	 * Current session count exceeds configured maximum.
	 * Continue on to verify the sessions if configured otherwise stop here.
	 */
	if (inst->verify_simul != true) {
		rcode = RLM_MODULE_OK;
		goto finish;
	}

	/* debugging */
	RDEBUG("verifying session count");

	/* reset the count */
	request->simul_count = 0;

	/* get client ip address for MPP detection below */
	if ((vp = fr_pair_find_by_num(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) {
		client_ip_addr = vp->vp_ipaddr;
	}

	/* get calling station id for MPP detection below */
	if ((vp = fr_pair_find_by_num(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) {
		client_cs_id = vp->vp_strvalue;
	}

	/* loop across all row elements */
	for (idx = 0; idx < json_object_array_length(jrows); idx++) {
		/* clear docid */
		memset(docid, 0, sizeof(docid));

		/* fetch current index */
		json = json_object_array_get_idx(jrows, idx);

		/* get document id */
		if (json_object_object_get_ex(json, "id", &jval)) {
			/* copy and check length */
			if (strlcpy(docid, json_object_get_string(jval), sizeof(docid)) >= sizeof(docid)) {
				RERROR("document id from row longer than MAX_KEY_SIZE (%d)", MAX_KEY_SIZE);
				continue;
			}
		}

		/* check for valid doc id */
		if (docid[0] == 0) {
			RWARN("failed to fetch document id from row - skipping");
			continue;
		}

		/* fetch document */
		cb_error = couchbase_get_key(cb_inst, cookie, docid);

		/* check error and object */
		if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) {
			/* log error */
			RERROR("failed to execute get request or parse return");
			/* set return */
			rcode = RLM_MODULE_FAIL;
			/* return */
			goto finish;
		}

		/* debugging */
		RDEBUG3("cookie->jobj == %s", json_object_to_json_string(cookie->jobj));

		/* get element name for User-Name attribute */
		if (mod_attribute_to_element("User-Name", inst->map, &element) == 0) {
			/* get and check username element */
			if (!json_object_object_get_ex(cookie->jobj, element, &jval)){
				RDEBUG("cannot zap stale entry without username");
				rcode = RLM_MODULE_FAIL;
				goto finish;
			}
			/* copy json string value to user_name */
			user_name = talloc_typed_strdup(request, json_object_get_string(jval));
		} else {
			RDEBUG("failed to find map entry for User-Name attribute");
			rcode = RLM_MODULE_FAIL;
			goto finish;
		}

		/* get element name for Acct-Session-Id attribute */
		if (mod_attribute_to_element("Acct-Session-Id", inst->map, &element) == 0) {
			/* get and check session id element */
			if (!json_object_object_get_ex(cookie->jobj, element, &jval)){
				RDEBUG("cannot zap stale entry without session id");
				rcode = RLM_MODULE_FAIL;
				goto finish;
			}
			/* copy json string value to session_id */
			session_id = talloc_typed_strdup(request, json_object_get_string(jval));
		} else {
			RDEBUG("failed to find map entry for Acct-Session-Id attribute");
			rcode = RLM_MODULE_FAIL;
			goto finish;
		}

		/* get element name for NAS-IP-Address attribute */
		if (mod_attribute_to_element("NAS-IP-Address", inst->map, &element) == 0) {
			/* attempt to get and nas address element */
			if (json_object_object_get_ex(cookie->jobj, element, &jval)){
				nas_addr = inet_addr(json_object_get_string(jval));
			}
		}

		/* get element name for NAS-Port attribute */
		if (mod_attribute_to_element("NAS-Port", inst->map, &element) == 0) {
			/* attempt to get nas port element */
			if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
				nas_port = (uint32_t) json_object_get_int(jval);
			}
		}

		/* check terminal server */
		int check = rad_check_ts(nas_addr, nas_port, user_name, session_id);

		/* take action based on check return */
		if (check == 0) {
			/* stale record - zap it if enabled */
			if (inst->delete_stale_sessions) {
				/* get element name for Framed-IP-Address attribute */
				if (mod_attribute_to_element("Framed-IP-Address", inst->map, &element) == 0) {
					/* attempt to get framed ip address element */
					if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
						framed_ip_addr = inet_addr(json_object_get_string(jval));
					}
				}

				/* get element name for Framed-Port attribute */
				if (mod_attribute_to_element("Framed-Port", inst->map, &element) == 0) {
					/* attempt to get framed port element */
					if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
						if (strcmp(json_object_get_string(jval), "PPP") == 0) {
							framed_proto = 'P';
						} else if (strcmp(json_object_get_string(jval), "SLIP") == 0) {
							framed_proto = 'S';
						}
					}
				}

				/* get element name for Acct-Session-Time attribute */
				if (mod_attribute_to_element("Acct-Session-Time", inst->map, &element) == 0) {
					/* attempt to get session time element */
					if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
						session_time = json_object_get_int(jval);
					}
				}

				/* zap session */
				session_zap(request, nas_addr, nas_port, user_name, session_id,
					    framed_ip_addr, framed_proto, session_time);
			}
		} else if (check == 1) {
			/* user is still logged in - increase count */
			++request->simul_count;

			/* get element name for Framed-IP-Address attribute */
			if (mod_attribute_to_element("Framed-IP-Address", inst->map, &element) == 0) {
				/* attempt to get framed ip address element */
				if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
					framed_ip_addr = inet_addr(json_object_get_string(jval));
				} else {
					/* ensure 0 if not found */
					framed_ip_addr = 0;
				}
			}

			/* get element name for Calling-Station-Id attribute */
			if (mod_attribute_to_element("Calling-Station-Id", inst->map, &element) == 0) {
				/* attempt to get framed ip address element */
				if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
					/* copy json string value to cs_id */
					cs_id = talloc_typed_strdup(request, json_object_get_string(jval));
				} else {
					/* ensure null if not found */
					cs_id = NULL;
				}
			}

			/* Does it look like a MPP attempt? */
			if (client_ip_addr && framed_ip_addr && framed_ip_addr == client_ip_addr) {
				request->simul_mpp = 2;
			} else if (client_cs_id && cs_id && !strncmp(cs_id, client_cs_id, 16)) {
				request->simul_mpp = 2;
			}

		} else {
			/* check failed - return error */
			REDEBUG("failed to check the terminal server for user '%s'", user_name);
			rcode = RLM_MODULE_FAIL;
			goto finish;
		}

		/* free and reset document user name talloc */
		if (user_name) TALLOC_FREE(user_name);

		/* free and reset document calling station id talloc */
		if (cs_id) TALLOC_FREE(cs_id);

		/* free and reset document session id talloc */
		if (session_id) TALLOC_FREE(session_id);

		/* free and reset json object before fetching next row */
		if (cookie->jobj) {
			json_object_put(cookie->jobj);
			cookie->jobj = NULL;
		}
	}

	/* debugging */
	RDEBUG("Retained %d open sessions for %s after verification",
	       request->simul_count, request->username->vp_strvalue);

finish:
	if (user_name) talloc_free(user_name);
	if (cs_id) talloc_free(cs_id);
	if (session_id) talloc_free(session_id);

	/* free rows */
	if (jrows) json_object_put(jrows);

	/* free and reset json object */
	if (cookie->jobj) {
		json_object_put(cookie->jobj);
		cookie->jobj = NULL;
	}

	if (handle) fr_connection_release(inst->pool, handle);

	/*
	 * The Auth module apparently looks at request->simul_count,
	 * not the return value of this module when deciding to deny
	 * a call for too many sessions.
	 */
	return rcode;
}