static void ippool_action_print(REQUEST *request, ippool_action_t action,
				fr_log_lvl_t lvl,
				uint8_t const *key_prefix, size_t key_prefix_len,
				char const *ip_str,
				uint8_t const *device_id, size_t device_id_len,
				uint8_t const *gateway_id, size_t gateway_id_len,
				uint32_t expires)
{
	char *key_prefix_str, *device_str = NULL, *gateway_str = NULL;

	key_prefix_str = fr_asprint(request, (char const *)key_prefix, key_prefix_len, '"');
	if (gateway_id) gateway_str = fr_asprint(request, (char const *)gateway_id, gateway_id_len, '"');
	if (device_id) device_str = fr_asprint(request, (char const *)device_id, device_id_len, '"');

	switch (action) {
	case POOL_ACTION_ALLOCATE:
		RDEBUGX(lvl, "Allocating lease from pool \"%s\"%s%s%s%s%s%s, expires in %us",
			key_prefix_str,
			device_str ? ", to \"" : "", device_str ? device_str : "",
			device_str ? "\"" : "",
			gateway_str ? ", on \"" : "", gateway_str ? gateway_str : "",
			gateway_str ? "\"" : "",
			expires);
		break;

	case POOL_ACTION_UPDATE:
		RDEBUGX(lvl, "Updating %s in pool \"%s\"%s%s%s%s%s%s, expires in %us",
			ip_str, key_prefix_str,
			device_str ? ", device \"" : "", device_str ? device_str : "",
			device_str ? "\"" : "",
			gateway_str ? ", gateway \"" : "", gateway_str ? gateway_str : "",
			gateway_str ? "\"" : "",
			expires);
		break;

	case POOL_ACTION_RELEASE:
		RDEBUGX(lvl, "Releasing %s%s%s%s to pool \"%s\"",
			ip_str,
			device_str ? " leased by \"" : "", device_str ? device_str : "",
			device_str ? "\"" : "",
			key_prefix_str);
		break;

	default:
		break;
	}

	/*
	 *	Ordering is important, needs to be LIFO
	 *	for proper talloc pool re-use.
	 */
	talloc_free(key_prefix_str);
	talloc_free(device_str);
	talloc_free(gateway_str);
}
Example #2
0
/** Converts a string value into a #VALUE_PAIR
 *
 * @param[in,out] ctx to allocate #VALUE_PAIR (s).
 * @param[out] out where to write the resulting #VALUE_PAIR.
 * @param[in] request The current request.
 * @param[in] map to process.
 * @param[in] uctx The value to parse.
 * @return
 *	- 0 on success.
 *	- -1 on failure.
 */
static int _sql_map_proc_get_value(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx)
{
	VALUE_PAIR	*vp;
	char const	*value = uctx;

	vp = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
	/*
	 *	Buffer not always talloced, sometimes it's
	 *	just a pointer to a field in a result struct.
	 */
	if (fr_pair_value_from_str(vp, value, strlen(value)) < 0) {
		char *escaped;

		escaped = fr_asprint(vp, value, talloc_array_length(value), '"');
		REDEBUG("Failed parsing value \"%s\" for attribute %s: %s", escaped,
			map->lhs->tmpl_da->name, fr_strerror());
		talloc_free(vp);

		return -1;
	}

	vp->op = map->op;
	*out = vp;

	return 0;
}
/** Locate a cache entry in redis
 *
 * @copydetails cache_entry_find_t
 */
static cache_status_t cache_entry_find(rlm_cache_entry_t **out,
				       UNUSED rlm_cache_config_t const *config, void *driver_inst,
				       REQUEST *request, UNUSED void *handle, uint8_t const *key, size_t key_len)
{
	rlm_cache_redis_t		*driver = driver_inst;
	size_t				i;

	fr_redis_cluster_state_t	state;
	fr_redis_conn_t			*conn;
	fr_redis_rcode_t		status;
	redisReply			*reply = NULL;
	int				s_ret;

	vp_map_t			*head = NULL, **last = &head;
#ifdef HAVE_TALLOC_POOLED_OBJECT
	size_t				pool_size = 0;
#endif
	rlm_cache_entry_t		*c;

	for (s_ret = fr_redis_cluster_state_init(&state, &conn, driver->cluster, request, key, key_len, false);
	     s_ret == REDIS_RCODE_TRY_AGAIN;	/* Continue */
	     s_ret = fr_redis_cluster_state_next(&state, &conn, driver->cluster, request, status, &reply)) {
		/*
		 *	Grab all the data for this hash, should return an array
		 *	of alternating keys/values which we then convert into maps.
		 */
		if (RDEBUG_ENABLED3) {
			char *p;

			p = fr_asprint(NULL, (char const *)key, key_len, '"');
			RDEBUG3("LRANGE %s 0 -1", key);
			talloc_free(p);
		}
		reply = redisCommand(conn->handle, "LRANGE %b 0 -1", key, key_len);
		status = fr_redis_command_status(conn, reply);
	}
	if (s_ret != REDIS_RCODE_SUCCESS) {
		RERROR("Failed retrieving entry");
		fr_redis_reply_free(reply);
		return CACHE_ERROR;
	}
	rad_assert(reply);	/* clang scan */

	if (reply->type != REDIS_REPLY_ARRAY) {
		REDEBUG("Bad result type, expected array, got %s",
			fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>"));
		fr_redis_reply_free(reply);
		return CACHE_ERROR;
	}

	RDEBUG3("Entry contains %zu elements", reply->elements);

	if (reply->elements == 0) {
		fr_redis_reply_free(reply);
		return CACHE_MISS;
	}

	if (reply->elements % 3) {
		REDEBUG("Invalid number of reply elements (%zu).  "
			"Reply must contain triplets of keys operators and values",
			reply->elements);
		fr_redis_reply_free(reply);
		return CACHE_ERROR;
	}

#ifdef HAVE_TALLOC_POOLED_OBJECT
	/*
	 *	We can get a pretty good idea of the required size of the pool
	 */
	for (i = 0; i < reply->elements; i += 3) {
		pool_size += sizeof(vp_map_t) + (sizeof(vp_tmpl_t) * 2);
		if (reply->element[i]->type == REDIS_REPLY_STRING) pool_size += reply->element[i]->len + 1;
	}

	/*
	 *	reply->elements gives us the number of chunks, as the maps are triplets, and there
	 *	are three chunks per map
	 */

	c = talloc_pooled_object(NULL,  rlm_cache_entry_t, reply->elements, pool_size);
	memset(&pool, 0, sizeof(rlm_cache_entry_t));
#else
	c = talloc_zero(NULL, rlm_cache_entry_t);
#endif
	/*
	 *	Convert the key/value pairs back into maps
	 */
	for (i = 0; i < reply->elements; i += 3) {
		if (fr_redis_reply_to_map(c, last, request,
					  reply->element[i], reply->element[i + 1], reply->element[i + 2]) < 0) {
			talloc_free(c);
			fr_redis_reply_free(reply);
			return CACHE_ERROR;
		}
		last = &(*last)->next;
	}
	fr_redis_reply_free(reply);

	/*
	 *	Pull out the cache created date
	 */
	if ((head->lhs->tmpl_da->vendor == 0) && (head->lhs->tmpl_da->attr == PW_CACHE_CREATED)) {
		vp_map_t *map;

		c->created = head->rhs->tmpl_data_value.date;

		map = head;
		head = head->next;
		talloc_free(map);
	}

	/*
	 *	Pull out the cache expires date
	 */
	if ((head->lhs->tmpl_da->vendor == 0) && (head->lhs->tmpl_da->attr == PW_CACHE_EXPIRES)) {
		vp_map_t *map;

		c->expires = head->rhs->tmpl_data_value.date;

		map = head;
		head = head->next;
		talloc_free(map);
	}

	c->key = talloc_memdup(c, key, key_len);
	c->key_len = key_len;
	c->maps = head;
	*out = c;

	return CACHE_OK;
}
/** Insert a new entry into the data store
 *
 * @copydetails cache_entry_insert_t
 */
static cache_status_t cache_entry_insert(UNUSED rlm_cache_config_t const *config, void *driver_inst,
					 REQUEST *request, UNUSED void *handle, const rlm_cache_entry_t *c)
{
	rlm_cache_redis_t	*driver = driver_inst;
	TALLOC_CTX		*pool;

	vp_map_t		*map;

	fr_redis_conn_t		*conn;
	fr_redis_cluster_state_t	state;
	fr_redis_rcode_t	status;
	redisReply		*reply = NULL;
	int			s_ret;

	static char const	command[] = "RPUSH";
	char const		**argv;
	size_t			*argv_len;
	char const		**argv_p;
	size_t			*argv_len_p;

	int			pipelined = 0;	/* How many commands pending in the pipeline */
	redisReply		*replies[5];	/* Should have the same number of elements as pipelined commands */
	size_t			reply_num = 0, i;

	char			*p;
	int			cnt;

	vp_tmpl_t		expires_value;
	vp_map_t		expires = {
					.op	= T_OP_SET,
					.lhs	= &driver->expires_attr,
					.rhs	= &expires_value,
				};

	vp_tmpl_t		created_value;
	vp_map_t		created = {
					.op	= T_OP_SET,
					.lhs	= &driver->created_attr,
					.rhs	= &created_value,
					.next	= &expires
				};

	/*
	 *	Encode the entry created date
	 */
	tmpl_init(&created_value, TMPL_TYPE_DATA, "<TEMP>", 6, T_BARE_WORD);
	created_value.tmpl_data_type = PW_TYPE_DATE;
	created_value.tmpl_data_length = sizeof(created_value.tmpl_data_value.date);
	created_value.tmpl_data_value.date = c->created;

	/*
	 *	Encode the entry expiry time
	 *
	 *	Although Redis objects expire on their own, we still need this
	 *	to ignore entries that were created before the last epoch.
	 */
	tmpl_init(&expires_value, TMPL_TYPE_DATA, "<TEMP>", 6, T_BARE_WORD);
	expires_value.tmpl_data_type = PW_TYPE_DATE;
	expires_value.tmpl_data_length = sizeof(expires_value.tmpl_data_value.date);
	expires_value.tmpl_data_value.date = c->expires;
	expires.next = c->maps;	/* Head of the list */

	for (cnt = 0, map = &created; map; cnt++, map = map->next);

	/*
	 *	The majority of serialized entries should be under 1k.
	 *
	 * @todo We should really calculate this using some sort of moving average.
	 */
	pool = talloc_pool(request, 1024);
	if (!pool) return CACHE_ERROR;

	argv_p = argv = talloc_array(pool, char const *, (cnt * 3) + 2);	/* pair = 3 + cmd + key */
	argv_len_p = argv_len = talloc_array(pool, size_t, (cnt * 3) + 2);	/* pair = 3 + cmd + key */

	*argv_p++ = command;
	*argv_len_p++ = sizeof(command) - 1;

	*argv_p++ = (char const *)c->key;
	*argv_len_p++ = c->key_len;

	/*
	 *	Add the maps to the command string in reverse order
	 */
	for (map = &created; map; map = map->next) {
		if (fr_redis_tuple_from_map(pool, argv_p, argv_len_p, map) < 0) {
			REDEBUG("Failed encoding map as Redis K/V pair");
			talloc_free(pool);
			return CACHE_ERROR;
		}
		argv_p += 3;
		argv_len_p += 3;
	}

	RDEBUG3("Pipelining commands");
	RINDENT();

	for (s_ret = fr_redis_cluster_state_init(&state, &conn, driver->cluster, request, c->key, c->key_len, false);
	     s_ret == REDIS_RCODE_TRY_AGAIN;	/* Continue */
	     s_ret = fr_redis_cluster_state_next(&state, &conn, driver->cluster, request, status, &reply)) {
		/*
		 *	Start the transaction, as we need to set an expiry time too.
		 */
		if (c->expires > 0) {
			RDEBUG3("MULTI");
			if (redisAppendCommand(conn->handle, "MULTI") != REDIS_OK) {
			append_error:
				REXDENT();
				RERROR("Failed appending Redis command to output buffer: %s", conn->handle->errstr);
				talloc_free(pool);
				return CACHE_ERROR;
			}
			pipelined++;
		}

		if (RDEBUG_ENABLED3) {
			p = fr_asprint(request, (char const *)c->key, c->key_len, '\0');
			RDEBUG3("DEL \"%s\"", p);
			talloc_free(p);

		}

		if (redisAppendCommand(conn->handle, "DEL %b", c->key, c->key_len) != REDIS_OK) goto append_error;
		pipelined++;

		if (RDEBUG_ENABLED3) {
			RDEBUG3("argv command");
			RINDENT();
			for (i = 0; i < talloc_array_length(argv); i++) {
				p = fr_asprint(request, argv[i], argv_len[i], '\0');
				RDEBUG3("%s", p);
				talloc_free(p);
			}
			REXDENT();
		}
		redisAppendCommandArgv(conn->handle, talloc_array_length(argv), argv, argv_len);
		pipelined++;

		/*
		 *	Set the expiry time and close out the transaction.
		 */
		if (c->expires > 0) {
			if (RDEBUG_ENABLED3) {
				p = fr_asprint(request, (char const *)c->key, c->key_len, '\"');
				RDEBUG3("EXPIREAT \"%s\" %li", p, (long)c->expires);
				talloc_free(p);
			}
			if (redisAppendCommand(conn->handle, "EXPIREAT %b %i", c->key,
					       c->key_len, c->expires) != REDIS_OK) goto append_error;
			pipelined++;
			RDEBUG3("EXEC");
			if (redisAppendCommand(conn->handle, "EXEC") != REDIS_OK) goto append_error;
			pipelined++;
		}
		REXDENT();

		reply_num = fr_redis_pipeline_result(&status, replies, sizeof(replies) / sizeof(*replies),
						     conn, pipelined);
		reply = replies[0];
	}
	talloc_free(pool);

	if (s_ret != REDIS_RCODE_SUCCESS) {
		RERROR("Failed inserting entry");
		return CACHE_ERROR;
	}

	RDEBUG3("Command results");
	RINDENT();
	for (i = 0; i < reply_num; i++) {
		fr_redis_reply_print(L_DBG_LVL_3, replies[i], request, i);
		fr_redis_reply_free(replies[i]);
	}
	REXDENT();

	return CACHE_OK;
}

/** Call delete the cache entry from redis
 *
 * @copydetails cache_entry_expire_t
 */
static cache_status_t cache_entry_expire(UNUSED rlm_cache_config_t const *config, void *driver_inst,
					 REQUEST *request, UNUSED void *handle,  uint8_t const *key, size_t key_len)
{
	rlm_cache_redis_t		*driver = driver_inst;
	fr_redis_cluster_state_t	state;
	fr_redis_conn_t			*conn;
	fr_redis_rcode_t			status;
	redisReply			*reply = NULL;
	int				s_ret;

	for (s_ret = fr_redis_cluster_state_init(&state, &conn, driver->cluster, request, key, key_len, false);
	     s_ret == REDIS_RCODE_TRY_AGAIN;	/* Continue */
	     s_ret = fr_redis_cluster_state_next(&state, &conn, driver->cluster, request, status, &reply)) {
	     	reply = redisCommand(conn->handle, "DEL %b", key, key_len);
	     	status = fr_redis_command_status(conn, reply);
	}
	if (s_ret != REDIS_RCODE_SUCCESS) {
		RERROR("Failed expiring entry");
		fr_redis_reply_free(reply);
		return CACHE_ERROR;
	}

	rad_assert(reply);	/* clang scan */
	if (reply->type == REDIS_REPLY_INTEGER) {
		fr_redis_reply_free(reply);
		if (reply->integer) return CACHE_OK;	/* Affected */
		return CACHE_MISS;
	}

	REDEBUG("Bad result type, expected integer, got %s",
		fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>"));
	fr_redis_reply_free(reply);

	return CACHE_ERROR;
}

extern cache_driver_t rlm_cache_redis;
cache_driver_t rlm_cache_redis = {
	.name		= "rlm_cache_redis",
	.instantiate	= mod_instantiate,
	.inst_size	= sizeof(rlm_cache_redis_t),
	.free		= cache_entry_free,

	.find		= cache_entry_find,
	.insert		= cache_entry_insert,
	.expire		= cache_entry_expire,
};
Example #5
0
/** Initiate an LDAP interactive bind
 *
 * @param[in] inst rlm_ldap configuration.
 * @param[in] request Current request, this may be NULL, in which case all debug logging is done with radlog.
 * @param[in] conn to use. May change as this function calls functions which auto re-connect.
 * @param[in] identity of the user.
 * @param[in] password of the user.
 * @param[in] sasl mechanism to use for bind, and additional parameters.
 * @param[in] serverctrls Search controls to pass to the server.  May be NULL.
 * @param[in] clientctrls Search controls for ldap_sasl_interactive.  May be NULL.
 * @param[out] error message resulting from bind.
 * @param[out] extra information about the error.
 * @return One of the LDAP_PROC_* (#ldap_rcode_t) values.
 */
ldap_rcode_t rlm_ldap_sasl_interactive(rlm_ldap_t const *inst, REQUEST *request,
				       ldap_handle_t *conn, char const *identity,
				       char const *password, ldap_sasl *sasl,
				       LDAPControl **serverctrls, LDAPControl **clientctrls,
				       char const **error, char **extra)
{
	ldap_rcode_t		status;
	int			ret = 0;
	int			msgid;
	char const		*mech;
	LDAPMessage		*result = NULL;
	rlm_ldap_sasl_ctx_t	sasl_ctx;		/* SASL defaults */

	LDAPControl		*our_serverctrls[LDAP_MAX_CONTROLS];
	LDAPControl		*our_clientctrls[LDAP_MAX_CONTROLS];

	rlm_ldap_control_merge(our_serverctrls, our_clientctrls,
			       sizeof(our_serverctrls) / sizeof(*our_serverctrls),
			       sizeof(our_clientctrls) / sizeof(*our_clientctrls),
			       conn, serverctrls, clientctrls);

	/* rlm_ldap_result may not be called */
	if (error) *error = NULL;
	if (extra) *extra = NULL;

	sasl_ctx.inst = inst;
	sasl_ctx.request = request;
	sasl_ctx.identity = identity;
	sasl_ctx.password = password;
	sasl_ctx.extra = sasl;

	MOD_ROPTIONAL(RDEBUG2, DEBUG2, "Starting SASL mech(s): %s", sasl->mech);
	for (;;) {
		ret = ldap_sasl_interactive_bind(conn->handle, NULL, sasl->mech,
						 our_serverctrls, our_clientctrls,
						 LDAP_SASL_AUTOMATIC,
						 _sasl_interact, &sasl_ctx, result,
						 &mech, &msgid);

		/*
		 *	If ldap_sasl_interactive_bind indicates it didn't want
		 *	to continue, then we're done.
		 *
		 *	Calling ldap_result here, results in a timeout in some
		 *	cases, so we need to figure out whether the bind was
		 *	successful without the help of ldap_result.
		 */
		if (ret != LDAP_SASL_BIND_IN_PROGRESS) {
			status = rlm_ldap_result(inst, conn, -1, identity, NULL, error, extra);
			break;		/* Old result gets freed on after exit */
		}

		ldap_msgfree(result);	/* We always need to free the old message */

		/*
		 *	If LDAP parse result indicates there was an error
		 *	then we're done.
		 */
		status = rlm_ldap_result(inst, conn, msgid, identity, &result, error, extra);
		switch (status) {
		case LDAP_PROC_SUCCESS:		/* ldap_sasl_interactive_bind should have indicated success */
		case LDAP_PROC_CONTINUE:
			break;

		default:
			goto done;
		}

		/*
		 *	...otherwise, the bind is still in progress.
		 */
		MOD_ROPTIONAL(RDEBUG3, DEBUG3, "Continuing SASL mech %s...", mech);

		/*
		 *	Write the servers response to the debug log
		 */
		if (((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) && result) {
			struct berval *srv_cred;

			if (ldap_parse_sasl_bind_result(conn->handle, result, &srv_cred, 0) == 0) {
				char *escaped;

				escaped = fr_asprint(request, srv_cred->bv_val, srv_cred->bv_len, '\0');
				MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL response  : %s", escaped);

				talloc_free(escaped);
				ldap_memfree(srv_cred);
			}
		}
	}
done:
	ldap_msgfree(result);

	return status;
}
Example #6
0
/** Callback for map_to_request
 *
 * Performs exactly the same job as map_to_vp, but pulls attribute values from LDAP entries
 *
 * @see map_to_vp
 */
int rlm_ldap_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx)
{
	rlm_ldap_result_t *self = uctx;
	VALUE_PAIR *head = NULL, *vp;
	vp_cursor_t cursor;
	int i;

	fr_cursor_init(&cursor, &head);

	switch (map->lhs->type) {
	/*
	 *	This is a mapping in the form of:
	 *		<list>: += <ldap attr>
	 *
	 *	Where <ldap attr> is:
	 *		<list>:<attr> <op> <value>
	 *
	 *	It is to allow for legacy installations which stored
	 *	RADIUS control and reply attributes in separate LDAP
	 *	attributes.
	 */
	case TMPL_TYPE_LIST:
		for (i = 0; i < self->count; i++) {
			vp_map_t *attr = NULL;

			RDEBUG3("Parsing valuepair string \"%s\"", self->values[i]->bv_val);
			if (map_afrom_attr_str(ctx, &attr, self->values[i]->bv_val,
					       map->lhs->tmpl_request, map->lhs->tmpl_list,
					       REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) {
				RWDEBUG("Failed parsing \"%s\" as valuepair (%s), skipping...", fr_strerror(),
					self->values[i]->bv_val);
				continue;
			}

			if (attr->lhs->tmpl_request != map->lhs->tmpl_request) {
				RWDEBUG("valuepair \"%s\" has conflicting request qualifier (%s vs %s), skipping...",
					self->values[i]->bv_val,
					fr_int2str(request_refs, attr->lhs->tmpl_request, "<INVALID>"),
					fr_int2str(request_refs, map->lhs->tmpl_request, "<INVALID>"));
			next_pair:
				talloc_free(attr);
				continue;
			}

			if ((attr->lhs->tmpl_list != map->lhs->tmpl_list)) {
				RWDEBUG("valuepair \"%s\" has conflicting list qualifier (%s vs %s), skipping...",
					self->values[i]->bv_val,
					fr_int2str(pair_lists, attr->lhs->tmpl_list, "<INVALID>"),
					fr_int2str(pair_lists, map->lhs->tmpl_list, "<INVALID>"));
				goto next_pair;
			}

			if (map_to_vp(request, &vp, request, attr, NULL) < 0) {
				RWDEBUG("Failed creating attribute for valuepair \"%s\", skipping...",
					self->values[i]->bv_val);
				goto next_pair;
			}

			fr_cursor_merge(&cursor, vp);
			talloc_free(attr);

			/*
			 *	Only process the first value, unless the operator is +=
			 */
			if (map->op != T_OP_ADD) break;
		}
		break;

	/*
	 *	Iterate over all the retrieved values,
	 *	don't try and be clever about changing operators
	 *	just use whatever was set in the attribute map.
	 */
	case TMPL_TYPE_ATTR:
		for (i = 0; i < self->count; i++) {
			if (!self->values[i]->bv_len) continue;

			vp = fr_pair_afrom_da(ctx, map->lhs->tmpl_da);
			rad_assert(vp);

			if (fr_pair_value_from_str(vp, self->values[i]->bv_val, self->values[i]->bv_len) < 0) {
				char *escaped;

				escaped = fr_asprint(vp, self->values[i]->bv_val, self->values[i]->bv_len, '"');
				RWDEBUG("Failed parsing value \"%s\" for attribute %s: %s", escaped,
					map->lhs->tmpl_da->name, fr_strerror());

				talloc_free(vp); /* also frees escaped */
				continue;
			}

			vp->op = map->op;
			fr_cursor_insert(&cursor, vp);

			/*
			 *	Only process the first value, unless the operator is +=
			 */
			if (map->op != T_OP_ADD) break;
		}
		break;

	default:
		rad_assert(0);
	}

	*out = head;

	return 0;
}