Пример #1
0
static int _map_proc_client_get_vp(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request,
				   vp_map_t const *map, void *uctx)
{
	client_get_vp_ctx_t	*client = uctx;
	VALUE_PAIR		*head = NULL, *vp;
	fr_cursor_t		cursor;
	fr_dict_attr_t const	*da;
	CONF_PAIR const		*cp;

	rad_assert(ctx != NULL);

	fr_cursor_init(&cursor, &head);

	/*
	 *	FIXME: allow multiple entries.
	 */
	if (map->lhs->type == TMPL_TYPE_ATTR) {
		da = map->lhs->tmpl_da;
	} else {
		char *attr;

		if (tmpl_aexpand(ctx, &attr, request, map->lhs, NULL, NULL) <= 0) {
			RWDEBUG("Failed expanding string");
			return -1;
		}

		da = fr_dict_attr_by_name(request->dict, attr);
		if (!da) {
			RWDEBUG("No such attribute '%s'", attr);
			return -1;
		}

		talloc_free(attr);
	}

	for (cp = client->cp;
	     cp;
	     cp = cf_pair_find_next(client->cs, cp, client->field)) {
		char const *value = cf_pair_value(cp);

		MEM(vp = fr_pair_afrom_da(ctx, da));
		if (fr_pair_value_from_str(vp, value, talloc_array_length(value) - 1, '\0', false) < 0) {
			RWDEBUG("Failed parsing value \"%pV\" for attribute %s: %s", fr_box_strvalue(value),
				map->lhs->tmpl_da->name, fr_strerror());
			fr_pair_list_free(&head);
			talloc_free(vp);
			return -1;
		}

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

		if (map->op != T_OP_ADD) break;	/* Create multiple attribute for multiple CONF_PAIRs */
	}

	*out = head;

	return 0;
}
Пример #2
0
/*
 *	Convert field X to a VP.
 */
static int csv_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx)
{
	char const *str = uctx;
	VALUE_PAIR *head = NULL, *vp;
	vp_cursor_t cursor;
	DICT_ATTR const *da;

	rad_assert(ctx != NULL);
	fr_cursor_init(&cursor, &head);

	/*
	 *	FIXME: allow multiple entries.
	 */
	if (map->lhs->type == TMPL_TYPE_ATTR) {
		da = map->lhs->tmpl_da;

	} else {
		char *attr;

		if (tmpl_aexpand(ctx, &attr, request, map->lhs, NULL, NULL) <= 0) {
			RWDEBUG("Failed expanding string");
			return -1;
		}

		da = dict_attrbyname(attr);
		if (!da) {
			RWDEBUG("No such attribute '%s'", attr);
			return -1;
		}

		talloc_free(attr);
	}

	vp = pairalloc(ctx, da);
	rad_assert(vp);

	if (pairparsevalue(vp, str, talloc_array_length(str) - 1) < 0) {
		char *escaped;

		escaped = fr_aprints(vp, str, talloc_array_length(str) - 1, '\'');
		RWDEBUG("Failed parsing value \"%s\" for attribute %s: %s", escaped,
			map->lhs->tmpl_da->name, fr_strerror());

		talloc_free(vp); /* also frees escaped */
		return -1;
	}

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

	*out = head;
	return 0;
}
Пример #3
0
/** Perform a search and map the result of the search to server attributes
 *
 * @param[in] mod_inst #rlm_csv_t
 * @param[in] proc_inst mapping map entries to field numbers.
 * @param[in,out] request The current request.
 * @param[in] key key to look for
 * @param[in] maps Head of the map list.
 * @return
 *	- #RLM_MODULE_NOOP no rows were returned.
 *	- #RLM_MODULE_UPDATED if one or more #VALUE_PAIR were added to the #REQUEST.
 *	- #RLM_MODULE_FAIL if an error occurred.
 */
static rlm_rcode_t mod_map_proc(void *mod_inst, UNUSED void *proc_inst, REQUEST *request,
				char const *key, vp_map_t const *maps)
{
	rlm_csv_t		*inst = mod_inst;
	rlm_csv_entry_t		*e, my_entry;
	vp_map_t const		*map;

	my_entry.key = key;

	e = rbtree_finddata(inst->tree, &my_entry);
	if (!e) return RLM_MODULE_NOOP;

	RINDENT();
	for (map = maps;
	     map != NULL;
	     map = map->next) {
		int field;
		char *field_name;

		/*
		 *	Avoid memory allocations if possible.
		 */
		if (map->rhs->type != TMPL_TYPE_LITERAL) {
			if (tmpl_aexpand(request, &field_name, request, map->rhs, NULL, NULL) < 0) {
				RDEBUG("Failed expanding RHS at %s", map->lhs->name);
				return RLM_MODULE_FAIL;
			}
		} else {
			memcpy(&field_name, &map->rhs->name, sizeof(field_name)); /* const */
		}

		field = fieldname2offset(inst, field_name);

		if (field_name != map->rhs->name) talloc_free(field_name);

		if (field < 0) {
			RDEBUG("No such field name %s", map->rhs->name);
			return RLM_MODULE_FAIL;
		}

		/*
		 *	Pass the raw data to the callback, which will
		 *	create the VP and add it to the map.
		 */
		if (map_to_request(request, map, csv_map_getvalue, e->data[field]) < 0) {
			return RLM_MODULE_FAIL;
		}
	}

	return RLM_MODULE_UPDATED;
}
Пример #4
0
/*
 *	Convert field X to a VP.
 */
static int csv_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx)
{
	char const		*str = uctx;
	VALUE_PAIR		*head = NULL, *vp;
	fr_cursor_t		cursor;
	fr_dict_attr_t		const *da;

	rad_assert(ctx != NULL);
	fr_cursor_init(&cursor, &head);

	/*
	 *	FIXME: allow multiple entries.
	 */
	if (map->lhs->type == TMPL_TYPE_ATTR) {
		da = map->lhs->tmpl_da;

	} else {
		char *attr;

		if (tmpl_aexpand(ctx, &attr, request, map->lhs, NULL, NULL) <= 0) {
			RWDEBUG("Failed expanding string");
			return -1;
		}


		da = fr_dict_attr_by_name(request->dict, attr);
		if (!da) {
			RWDEBUG("No such attribute '%s'", attr);
			return -1;
		}

		talloc_free(attr);
	}

	vp = fr_pair_afrom_da(ctx, da);
	rad_assert(vp);

	if (fr_pair_value_from_str(vp, str, talloc_array_length(str) - 1, '\0', true) < 0) {
		RWDEBUG("Failed parsing value \"%pV\" for attribute %s: %s", fr_box_strvalue_buffer(str),
			map->lhs->tmpl_da->name, fr_strerror());
		talloc_free(vp);

		return -1;
	}

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

	*out = head;
	return 0;
}
Пример #5
0
/** Evaluate a set of maps using the specified map processor
 *
 * Evaluate the map processor src template, then call a map processor function to do
 * something with the expanded src template and map the result to attributes in the request.
 *
 * @param request The current request.
 * @param inst of a map processor.
 */
rlm_rcode_t map_proc(REQUEST *request, map_proc_inst_t const *inst)
{
	char		*value;
	rlm_rcode_t	rcode;

	if (tmpl_aexpand(request, &value, request, inst->src, inst->proc->escape, inst->proc->mod_inst) < 0) {
		return RLM_MODULE_FAIL;
	}

	rcode = inst->proc->evaluate(inst->proc->mod_inst, inst->data, request, value, inst->maps);
	talloc_free(value);

	return rcode;
}
Пример #6
0
static rlm_rcode_t CC_HINT(nonnull) mod_delay(void *instance, UNUSED void *thread, REQUEST *request)
{
	rlm_delay_t const	*inst = instance;
	struct timeval		delay, resume_at, *yielded_at;

	if (inst->delay) {
		if (tmpl_aexpand(request, &delay, request, inst->delay, NULL, NULL) < 0) return RLM_MODULE_FAIL;
	} else {
		memset(&delay, 0, sizeof(delay));
	}

	/*
	 *	Record the time that we yielded the request
	 */
	MEM(yielded_at = talloc(request, struct timeval));
	if (gettimeofday(yielded_at, NULL) < 0) {
		REDEBUG("Failed getting current time: %s", fr_syserror(errno));
		return RLM_MODULE_FAIL;
	}

	/*
	 *	Setup the delay for this request
	 */
	if (delay_add(request, &resume_at, yielded_at, &delay, inst->force_reschedule, inst->delay) != 0) {
		return RLM_MODULE_NOOP;
	}

	RDEBUG3("Current time %pV, resume time %pV", fr_box_timeval(*yielded_at), fr_box_timeval(resume_at));

	if (unlang_event_module_timeout_add(request, _delay_done, yielded_at, &resume_at) < 0) {
		RPEDEBUG("Adding event failed");
		return RLM_MODULE_FAIL;
	}

	return unlang_module_yield(request, mod_delay_return, mod_delay_cancel, yielded_at);
}
Пример #7
0
/** Logging callback to write log messages to a destination
 *
 * This allows the logging destination to be customised on a per request basis.
 *
 * @note Function does not write log output immediately
 *
 * @param[in] type	What type of message this is (error, warn, info, debug).
 * @param[in] lvl	At what logging level this message should be output.
 * @param[in] request	The current request.
 * @param[in] file	src file the log message was generated in.
 * @param[in] line	number the log message was generated on.
 * @param[in] fmt	sprintf style fmt string.
 * @param[in] ap	Arguments for the fmt string.
 * @param[in] uctx	Context data for the log function.
 */
static void logtee_it(fr_log_type_t type, fr_log_lvl_t lvl, REQUEST *request,
		      UNUSED char const *file, UNUSED int line,
		      char const *fmt, va_list ap, void *uctx)
{
	rlm_logtee_thread_t	*t = talloc_get_type_abort(uctx, rlm_logtee_thread_t);
	rlm_logtee_t const	*inst = t->inst;
	char			*msg, *exp;
	fr_cursor_t		cursor;
	VALUE_PAIR		*vp;
	log_dst_t		*dst;

	rad_assert(t->msg->vp_length == 0);	/* Should have been cleared before returning */

	/*
	 *	None of this should involve mallocs unless msg > 1k
	 */
	msg = talloc_typed_vasprintf(t->msg, fmt, ap);
	fr_value_box_strdup_buffer_shallow(NULL, &t->msg->data, attr_log_message, msg, true);

	t->type->vp_uint32 = (uint32_t) type;
	t->lvl->vp_uint32 = (uint32_t) lvl;

	fr_cursor_init(&cursor, &request->packet->vps);
	fr_cursor_prepend(&cursor, t->msg);
	fr_cursor_prepend(&cursor, t->type);
	fr_cursor_prepend(&cursor, t->lvl);
	fr_cursor_head(&cursor);

	/*
	 *	Now expand our fmt string to encapsulate the
	 *	message and any metadata
	 *
	 *	Fixme: Would be better to call tmpl_expand
	 *	into a variable length ring buffer.
	 */
	dst = request->log.dst;
	request->log.dst = NULL;
	if (tmpl_aexpand(t, &exp, request, inst->log_fmt, NULL, NULL) < 0) goto finish;
	request->log.dst = dst;

	fr_fring_overwrite(t->fring, exp);	/* Insert it into the buffer */

	if (!t->pending) {
		t->pending = true;
		logtee_fd_active(t);		/* Listen for when the fd is writable */
	}

finish:
	/*
	 *	Don't free, we re-use the VALUE_PAIRs for the next message
	 */
	vp = fr_cursor_remove(&cursor);
	if (!fr_cond_assert(vp == t->lvl)) fr_cursor_append(&cursor, vp);

	vp = fr_cursor_remove(&cursor);
	if (!fr_cond_assert(vp == t->type)) fr_cursor_append(&cursor, vp);

	vp = fr_cursor_remove(&cursor);
	if (!fr_cond_assert(vp == t->msg)) fr_cursor_append(&cursor, vp);

	fr_value_box_clear(&t->msg->data);		/* Clear message data */
}
Пример #8
0
/** Create a new server TLS session
 *
 * Configures a new server TLS session, configuring options, setting callbacks etc...
 *
 * @param ctx		to alloc session data in. Should usually be NULL unless the lifetime of the
 *			session is tied to another talloc'd object.
 * @param conf		values for this TLS session.
 * @param request	The current #REQUEST.
 * @param client_cert	Whether to require a client_cert.
 * @return
 *	- A new session on success.
 *	- NULL on error.
 */
tls_session_t *tls_session_init_server(TALLOC_CTX *ctx, fr_tls_conf_t *conf, REQUEST *request, bool client_cert)
{
	tls_session_t	*session = NULL;
	SSL		*new_tls = NULL;
	int		verify_mode = 0;
	VALUE_PAIR	*vp;
	SSL_CTX		*ssl_ctx;

	rad_assert(request != NULL);
	rad_assert(conf->ctx_count > 0);

	RDEBUG2("Initiating new TLS session");

	ssl_ctx = conf->ctx[(conf->ctx_count == 1) ? 0 : conf->ctx_next++ % conf->ctx_count];	/* mutex not needed */
	rad_assert(ssl_ctx);

	new_tls = SSL_new(ssl_ctx);
	if (new_tls == NULL) {
		tls_log_error(request, "Error creating new TLS session");
		return NULL;
	}

	session = talloc_zero(ctx, tls_session_t);
	if (session == NULL) {
		RERROR("Error allocating memory for TLS session");
		SSL_free(new_tls);

		return NULL;
	}
	session_init(session);
	session->ctx = ssl_ctx;
	session->ssl = new_tls;

	talloc_set_destructor(session, _tls_session_free);

	/*
	 *	Initialize callbacks
	 */
	session->record_init = record_init;
	session->record_close = record_close;
	session->record_from_buff = record_from_buff;
	session->record_to_buff = record_to_buff;

	/*
	 *	Create & hook the BIOs to handle the dirty side of the
	 *	SSL.  This is *very important* as we want to handle
	 *	the transmission part.  Now the only IO interface
	 *	that SSL is aware of, is our defined BIO buffers.
	 *
	 *	This means that all SSL IO is done to/from memory,
	 *	and we can update those BIOs from the packets we've
	 *	received.
	 */
	session->into_ssl = BIO_new(BIO_s_mem());
	session->from_ssl = BIO_new(BIO_s_mem());
	SSL_set_bio(session->ssl, session->into_ssl, session->from_ssl);

	/*
	 *	Add the message callback to identify what type of
	 *	message/handshake is passed
	 */
	SSL_set_msg_callback(new_tls, tls_session_msg_cb);
	SSL_set_msg_callback_arg(new_tls, session);
	SSL_set_info_callback(new_tls, tls_session_info_cb);

	/*
	 *	This sets the context sessions can be resumed in.
	 *	This is to prevent sessions being created by one application
	 *	and used by another.  In our case it prevents sessions being
	 *	reused between modules, or TLS server components such as
	 *	RADSEC.
	 *
	 *	A context must always be set when doing session resumption
	 *	otherwise session resumption will fail.
	 *
	 *	As the context ID must be <= 32, we digest the context
	 *	data with sha256.
	 */
	rad_assert(conf->session_id_name);
	{
		char		*context_id;
		EVP_MD_CTX	*md_ctx;
		uint8_t		digest[SHA256_DIGEST_LENGTH];

		static_assert(sizeof(digest) <= SSL_MAX_SSL_SESSION_ID_LENGTH,
			      "SSL_MAX_SSL_SESSION_ID_LENGTH must be >= SHA256_DIGEST_LENGTH");

		if (tmpl_aexpand(session, &context_id, request, conf->session_id_name, NULL, NULL) < 0) {
			RPEDEBUG("Failed expanding session ID");
			talloc_free(session);
		}

		MEM(md_ctx = EVP_MD_CTX_create());
		EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL);
		EVP_DigestUpdate(md_ctx, context_id, talloc_array_length(context_id) - 1);
		EVP_DigestFinal_ex(md_ctx, digest, NULL);
		EVP_MD_CTX_destroy(md_ctx);
		talloc_free(context_id);

		if (!fr_cond_assert(SSL_set_session_id_context(session->ssl, digest, sizeof(digest)) == 1)) {
			talloc_free(session);
			return NULL;
		}
	}

	/*
	 *	Add the session certificate to the session.
	 */
	vp = fr_pair_find_by_da(request->control, attr_tls_session_cert_file, TAG_ANY);
	if (vp) {
		RDEBUG2("Loading TLS session certificate \"%s\"", vp->vp_strvalue);

		if (SSL_use_certificate_file(session->ssl, vp->vp_strvalue, SSL_FILETYPE_PEM) != 1) {
			tls_log_error(request, "Failed loading TLS session certificate \"%s\"",
				      vp->vp_strvalue);
			talloc_free(session);
			return NULL;
		}

		if (SSL_use_PrivateKey_file(session->ssl, vp->vp_strvalue, SSL_FILETYPE_PEM) != 1) {
			tls_log_error(request, "Failed loading TLS session certificate \"%s\"",
				      vp->vp_strvalue);
			talloc_free(session);
			return NULL;
		}

		if (SSL_check_private_key(session->ssl) != 1) {
			tls_log_error(request, "Failed validating TLS session certificate \"%s\"",
				      vp->vp_strvalue);
			talloc_free(session);
			return NULL;
		}
	/*
	 *	Better to perform explicit checks, than rely
	 *	on OpenSSL's opaque error messages.
	 */
	} else {
		if (!conf->chains || !conf->chains[0]->private_key_file) {
			ERROR("TLS Server requires a private key file");
			talloc_free(session);
			return NULL;
		}

		if (!conf->chains || !conf->chains[0]->certificate_file) {
			ERROR("TLS Server requires a certificate file");
			talloc_free(session);
			return NULL;
		}
	}

	/*
	 *	In Server mode we only accept.
	 *
	 *	This sets up the SSL session to work correctly with
	 *	tls_session_handhsake.
	 */
	SSL_set_accept_state(session->ssl);

	/*
	 *	Verify the peer certificate, if asked.
	 */
	if (client_cert) {
		RDEBUG2("Setting verify mode to require certificate from client");
		verify_mode = SSL_VERIFY_PEER;
		verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
		verify_mode |= SSL_VERIFY_CLIENT_ONCE;
	}
	SSL_set_verify(session->ssl, verify_mode, tls_validate_cert_cb);

	SSL_set_ex_data(session->ssl, FR_TLS_EX_INDEX_CONF, (void *)conf);
	SSL_set_ex_data(session->ssl, FR_TLS_EX_INDEX_TLS_SESSION, (void *)session);

	/*
	 *	We use default fragment size, unless the Framed-MTU
	 *	tells us it's too big.  Note that we do NOT account
	 *	for the EAP-TLS headers if conf->fragment_size is
	 *	large, because that config item looks to be confusing.
	 *
	 *	i.e. it should REALLY be called MTU, and the code here
	 *	should figure out what that means for TLS fragment size.
	 *	asking the administrator to know the internal details
	 *	of EAP-TLS in order to calculate fragment sizes is
	 *	just too much.
	 */
	session->mtu = conf->fragment_size;
	vp = fr_pair_find_by_da(request->packet->vps, attr_framed_mtu, TAG_ANY);
	if (vp && (vp->vp_uint32 > 100) && (vp->vp_uint32 < session->mtu)) {
		RDEBUG2("Setting fragment_len to %u from &Framed-MTU", vp->vp_uint32);
		session->mtu = vp->vp_uint32;
	}

	if (conf->session_cache_server) session->allow_session_resumption = true; /* otherwise it's false */

	return session;
}
Пример #9
0
/**
 * Main module procedure.
 * @param[in] instance Module instance.
 * @param[in] request Radius request.
 * @return
 */
static rlm_rcode_t mod_proc(void *instance, REQUEST *request) {
  rlm_mongodb_t *inst = instance;
  rlm_mongodb_conn_t *conn = NULL;
  rlm_rcode_t code = RLM_MODULE_FAIL;
  bson_error_t error;

  conn = fr_connection_get(inst->pool);
  if (!conn) {
    goto end;
  }

  if (inst->action == RLM_MONGODB_GET) {
    // TODO: implement me!
    code = RLM_MODULE_FAIL;
  } else {
    mongoc_collection_t *mongo_collection = NULL;
    char *db = NULL, *collection = NULL, *query = NULL, *sort = NULL, *update = NULL;
    bson_t *bson_query = NULL, *bson_sort = NULL, *bson_update = NULL;

    if (tmpl_aexpand(request, &db, request, inst->cfg.db, NULL, NULL) < 0) {
      ERROR("failed to substitute attributes for db '%s'", inst->cfg.db->name);
      goto end_set;
    }

    if (tmpl_aexpand(request, &collection, request, inst->cfg.collection, NULL, NULL) < 0) {
      ERROR("failed to substitute attributes for collection '%s'", inst->cfg.collection->name);
      goto end_set;
    }

    ssize_t query_len = tmpl_aexpand(request, &query, request, inst->cfg.search_query, NULL, NULL);
    if (query_len < 0) {
      ERROR("failed to substitute attributes for search query '%s'", inst->cfg.search_query->name);
      goto end_set;
    }
    bson_query = bson_new_from_json((uint8_t *) query, query_len, &error);
    if (!bson_query) {
      RERROR("JSON->BSON conversion failed for search query '%s': %d.%d %s",
             query, error.domain, error.code, error.message);
      goto end_set;
    }

    ssize_t sort_len = tmpl_aexpand(request, &sort, request, inst->cfg.sort_query, NULL, NULL);
    if (query_len < 0) {
      ERROR("failed to substitute attributes for sort query '%s'", inst->cfg.sort_query->name);
      goto end_set;
    }
    if (sort_len) {
      bson_sort = bson_new_from_json((uint8_t *) sort, sort_len, &error);
      if (!bson_sort) {
        RERROR("JSON->BSON conversion failed for sort query '%s': %d.%d %s",
               sort, error.domain, error.code, error.message);
        goto end_set;
      }
    }

    ssize_t update_len = tmpl_aexpand(request, &update, request, inst->cfg.update_query, NULL, NULL);
    if (query_len < 0) {
      ERROR("failed to substitute attributes for update query '%s'", inst->cfg.update_query->name);
      goto end_set;
    }
    if (update_len) {
      bson_update = bson_new_from_json((uint8_t *) update, update_len, &error);
      if (!bson_update) {
        RERROR("JSON->BSON conversion failed for update query '%s': %d.%d %s",
               update, error.domain, error.code, error.message);
        goto end_set;
      }
    }

    mongo_collection = mongoc_client_get_collection(conn->client, db, collection);
    if (!mongo_collection) {
      RERROR("failed to get collection %s/%s", db, collection);
      goto end_set;
    }

    bool ok = mongoc_collection_find_and_modify(mongo_collection, bson_query, bson_sort,
                                                bson_update, NULL, inst->cfg.remove,
                                                inst->cfg.upsert, false, NULL, &error);

    code = ok ? RLM_MODULE_OK : RLM_MODULE_FAIL;

    end_set:
    if (mongo_collection) mongoc_collection_destroy(mongo_collection);
    if (bson_query) bson_destroy(bson_query);
    if (bson_sort) bson_destroy(bson_sort);
    if (bson_update) bson_destroy(bson_update);
  }

  end:
  if (conn) fr_connection_release(inst->pool, conn);
  return code;
}
Пример #10
0
/** Map multiple attributes from a client into the request
 *
 * @param[in] mod_inst		NULL.
 * @param[in] proc_inst		NULL.
 * @param[in] request		The current request.
 * @param[in] client_override	If NULL, use the current client, else use the client matching
 *				the ip given.
 * @param[in] maps		Head of the map list.
 * @return
 *	- #RLM_MODULE_NOOP no rows were returned.
 *	- #RLM_MODULE_UPDATED if one or more #VALUE_PAIR were added to the #REQUEST.
 *	- #RLM_MODULE_FAIL if an error occurred.
 */
static rlm_rcode_t map_proc_client(UNUSED void *mod_inst, UNUSED void *proc_inst, REQUEST *request,
				   fr_value_box_t **client_override, vp_map_t const *maps)
{
	rlm_rcode_t		rcode = RLM_MODULE_OK;
	vp_map_t const		*map;
	RADCLIENT		*client;
	client_get_vp_ctx_t	uctx;

	if (*client_override) {
		fr_ipaddr_t	ip;
		char const	*client_str;

		/*
		 *	Concat don't asprint, as this becomes a noop
		 *	in the vast majority of cases.
		 */
		if (fr_value_box_list_concat(request, *client_override, client_override, FR_TYPE_STRING, true) < 0) {
			REDEBUG("Failed concatenating input data");
			return RLM_MODULE_FAIL;
		}
		client_str = (*client_override)->vb_strvalue;

		if (fr_inet_pton(&ip, client_str, -1, AF_UNSPEC, false, true) < 0) {
			REDEBUG("\"%s\" is not a valid IPv4 or IPv6 address", client_str);
			rcode = RLM_MODULE_FAIL;
			goto finish;
		}

		client = client_find(NULL, &ip, IPPROTO_IP);
		if (!client) {
			RDEBUG("No client found with IP \"%s\"", client_str);
			rcode = RLM_MODULE_NOTFOUND;
			goto finish;
		}

		if (client->cs) {
			char const *filename;
			int line;

			filename = cf_filename(client->cs);
			line = cf_lineno(client->cs);

			if (filename) {
				RDEBUG2("Found client matching \"%s\".  Defined in \"%s\" line %i",
					client_str, filename, line);
			} else {
				RDEBUG2("Found client matching \"%s\"", client_str);
			}
		}
	} else {
		client = request->client;
	}
	uctx.cs = client->cs;

	RINDENT();
	for (map = maps;
	     map != NULL;
	     map = map->next) {
		char	*field = NULL;

		if (tmpl_aexpand(request, &field, request, map->rhs, NULL, NULL) < 0) {
			REDEBUG("Failed expanding RHS at %s", map->lhs->name);
			rcode = RLM_MODULE_FAIL;
			talloc_free(field);
			break;
		}

		uctx.cp = cf_pair_find(client->cs, field);
		if (!uctx.cp) {
			RDEBUG3("No matching client property \"%s\", skipping...", field);
			goto next;			/* No matching CONF_PAIR found */
		}
		uctx.field = field;

		/*
		 *	Pass the raw data to the callback, which will
		 *	create the VP and add it to the map.
		 */
		if (map_to_request(request, map, _map_proc_client_get_vp, &uctx) < 0) {
			rcode = RLM_MODULE_FAIL;
			talloc_free(field);
			break;
		}
		rcode = RLM_MODULE_UPDATED;

	next:
		talloc_free(field);
	}
	REXDENT();

finish:
	return rcode;
}