fr_ldap_result_code_t *ret; //!< Result of the query and applying the map.
int *applied; //!< Number of profiles applied.
fr_ldap_query_t *query;
- char const *dn;
+ char const *dn; //!< DN of the profile object being retrieved.
+ ///< NULL when all profiles are retrieved by one search.
rlm_ldap_t const *inst;
fr_ldap_map_exp_t const *expanded;
+ fr_ldap_thread_trunk_t *ttrunk; //!< Trunk used for profile searches.
+ char const *filter; //!< Filter to apply to profile searches.
+ char const * const *next; //!< Next profile DN to search for (search_mode = seq).
} ldap_profile_ctx_t;
+/** Apply the attribute map to a single profile entry
+ *
+ * @param[out] fallthrough Whether processing should continue to the next profile entry.
+ * @param[in] request Current request.
+ * @param[in] profile_ctx Profile lookup state.
+ * @param[in] handle libldap handle the result was received on.
+ * @param[in] entry Profile object to apply.
+ */
+static void ldap_profile_entry_map(bool *fallthrough, request_t *request, ldap_profile_ctx_t *profile_ctx,
+ LDAP *handle, LDAPMessage *entry)
+{
+ int ret;
+
+ // Set fallthrough to the configured default
+ *fallthrough = profile_ctx->inst->profile.fallthrough_def;
+
+ ret = fr_ldap_map_do(request, profile_ctx->inst->profile.check_attr, profile_ctx->inst->valuepair_attr,
+ profile_ctx->expanded, entry);
+ if (ret < 0) {
+ if (profile_ctx->ret) *profile_ctx->ret = LDAP_RESULT_ERROR;
+ } else {
+ if (profile_ctx->applied) *profile_ctx->applied += ret;
+ }
+
+ if (profile_ctx->inst->profile.fallthrough_attr) {
+ struct berval **values;
+ int count;
+ char *value;
+ xlat_exp_head_t *cond_expr = NULL;
+ fr_value_box_list_t res;
+
+ tmpl_rules_t const parse_rules = {
+ .attr = {
+ .dict_def = request->local_dict,
+ .list_def = request_attr_request,
+ },
+ .xlat = {
+ .runtime_el = unlang_interpret_event_list(request),
+ },
+ .at_runtime = true,
+ };
+
+ values = ldap_get_values_len(handle, entry, profile_ctx->inst->profile.fallthrough_attr);
+ count = ldap_count_values_len(values);
+ if (count == 0) goto free_values;
+ if (count > 1) {
+ RWARN("%s returned more than 1 value. Only evaluating the first.",
+ profile_ctx->inst->profile.fallthrough_attr);
+ }
+ value = fr_ldap_berval_to_string(request, values[0]);
+
+ RDEBUG3("Parsing fallthrough condition %s", value);
+ if (xlat_tokenize_expression(request, &cond_expr,
+ &FR_SBUFF_IN(value, talloc_strlen(value)),
+ NULL, &parse_rules) < 0) {
+ RPEDEBUG("Failed parsing '%s' value \"%s\"", profile_ctx->inst->profile.fallthrough_attr, value);
+ goto free;
+ }
+
+ if (xlat_impure_func(cond_expr)) {
+ fr_strerror_const("Fallthrough expression cannot depend on functions which call external databases");
+ goto free;
+ }
+
+ RDEBUG2("Checking fallthrough condition %s", value);
+ fr_value_box_list_init(&res);
+ if (unlang_xlat_eval(request, &res, request, cond_expr) < 0) {
+ RPEDEBUG("Failed evaluating condition");
+ goto free;
+ }
+ *fallthrough = (fr_value_box_list_head(&res) && fr_value_box_is_truthy(fr_value_box_list_head(&res))) ? true : false;
+ fr_value_box_list_talloc_free(&res);
+ RDEBUG2("Fallthrough condition evaluated to %s", *fallthrough ? "true" : "false");
+ free:
+ talloc_free(value);
+ talloc_free(cond_expr);
+ free_values:
+ ldap_value_free_len(values);
+ }
+}
+
/** Process the results of a profile lookup
*
*/
ldap_profile_ctx_t *profile_ctx = talloc_get_type_abort(uctx, ldap_profile_ctx_t);
fr_ldap_query_t *query = profile_ctx->query;
LDAP *handle;
- LDAPMessage *entry = NULL;
+ LDAPMessage *entry;
int ldap_errno;
char *dn = NULL;
- int ret;
- bool fallthrough;
+ bool fallthrough = true;
/*
* Tell the caller what happened
case LDAP_RESULT_NO_RESULT:
case LDAP_RESULT_BAD_DN:
- RDEBUG2("Profile object \"%s\" not found", profile_ctx->dn);
- goto finish;
+ if (profile_ctx->dn) {
+ RDEBUG2("Profile object \"%s\" not found", profile_ctx->dn);
+ } else {
+ RDEBUG2("No profile objects found");
+ }
+ goto next;
default:
goto finish;
ldap_memfree(dn);
}
- // Set fallthrough to the configured default
- fallthrough = profile_ctx->inst->profile.fallthrough_def;
-
RINDENT();
- ret = fr_ldap_map_do(request, profile_ctx->inst->profile.check_attr, profile_ctx->inst->valuepair_attr,
- profile_ctx->expanded, entry);
- if (ret < 0) {
- if (profile_ctx->ret) *profile_ctx->ret = LDAP_RESULT_ERROR;
- } else {
- if (profile_ctx->applied) *profile_ctx->applied += ret;
- }
-
- if (profile_ctx->inst->profile.fallthrough_attr) {
- struct berval **values;
- int count;
- char *value;
- xlat_exp_head_t *cond_expr = NULL;
- fr_value_box_list_t res;
-
- tmpl_rules_t const parse_rules = {
- .attr = {
- .dict_def = request->local_dict,
- .list_def = request_attr_request,
- },
- .xlat = {
- .runtime_el = unlang_interpret_event_list(request),
- },
- .at_runtime = true,
- };
-
- values = ldap_get_values_len(handle, entry, profile_ctx->inst->profile.fallthrough_attr);
- count = ldap_count_values_len(values);
- if (count == 0) goto free_values;
- if (count > 1) {
- RWARN("%s returned more than 1 value. Only evaluating the first.",
- profile_ctx->inst->profile.fallthrough_attr);
- }
- value = fr_ldap_berval_to_string(request, values[0]);
-
- RDEBUG3("Parsing fallthrough condition %s", value);
- if (xlat_tokenize_expression(request, &cond_expr,
- &FR_SBUFF_IN(value, talloc_strlen(value)),
- NULL, &parse_rules) < 0) {
- RPEDEBUG("Failed parsing '%s' value \"%s\"", profile_ctx->inst->profile.fallthrough_attr, value);
- goto free;
- }
-
- if (xlat_impure_func(cond_expr)) {
- fr_strerror_const("Fallthrough expression cannot depend on functions which call external databases");
- goto free;
- }
-
- RDEBUG2("Checking fallthrough condition %s", value);
- fr_value_box_list_init(&res);
- if (unlang_xlat_eval(request, &res, request, cond_expr) < 0) {
- RPEDEBUG("Failed evaluating condition");
- goto free;
- }
- fallthrough = (fr_value_box_list_head(&res) && fr_value_box_is_truthy(fr_value_box_list_head(&res))) ? true : false;
- fr_value_box_list_talloc_free(&res);
- RDEBUG2("Fallthrough condition evaluated to %s", fallthrough ? "true" : "false");
- free:
- talloc_free(value);
- talloc_free(cond_expr);
- free_values:
- ldap_value_free_len(values);
- }
-
- entry = ldap_next_entry(handle, entry);
+ ldap_profile_entry_map(&fallthrough, request, profile_ctx, handle, entry);
REXDENT();
if (!fallthrough) break;
+
+ entry = ldap_next_entry(handle, entry);
}
REXDENT();
+ if (!fallthrough) goto finish;
+
+next:
+ /*
+ * Chain the search for the next profile (search_mode = seq)
+ */
+ while (profile_ctx->next && *profile_ctx->next) {
+ LDAPControl *serverctrls[] = { profile_ctx->inst->profile.obj_sort_ctrl, NULL };
+
+ TALLOC_FREE(profile_ctx->query);
+
+ profile_ctx->dn = *profile_ctx->next++;
+
+ if (unlang_function_repeat_set(request, ldap_map_profile_resume) < 0) {
+ talloc_free(profile_ctx);
+ return UNLANG_ACTION_FAIL;
+ }
+ return fr_ldap_trunk_search(profile_ctx, &profile_ctx->query, request, profile_ctx->ttrunk,
+ profile_ctx->dn, profile_ctx->inst->profile.obj_scope,
+ profile_ctx->filter, profile_ctx->expanded->attrs, serverctrls, NULL);
+ }
+
finish:
talloc_free(profile_ctx);
return UNLANG_ACTION_CALCULATE_RESULT;
}
-/** Cancel an in progress profile lookup
+/** Push the resume frame and start the first profile search
*
+ * Cancellation is handled by the frame fr_ldap_trunk_search pushes above
+ * this one, which abandons the in-flight query and detaches it from the
+ * trunk request before the unwind frees profile_ctx.
*/
-static void ldap_map_profile_cancel(UNUSED request_t *request, UNUSED fr_signal_t action, void *uctx)
+static unlang_action_t ldap_profile_search_push(ldap_profile_ctx_t *profile_ctx, request_t *request,
+ char const *base, int scope, char const *filter)
{
- ldap_profile_ctx_t *profile_ctx = talloc_get_type_abort(uctx, ldap_profile_ctx_t);
+ LDAPControl *serverctrls[] = { profile_ctx->inst->profile.obj_sort_ctrl, NULL };
- if (!profile_ctx->query || !profile_ctx->query->treq) return;
+ if (unlang_function_push(request,
+ NULL,
+ ldap_map_profile_resume,
+ NULL, 0,
+ UNLANG_SUB_FRAME,
+ profile_ctx) < 0) {
+ talloc_free(profile_ctx);
+ return UNLANG_ACTION_FAIL;
+ }
- trunk_request_signal_cancel(profile_ctx->query->treq);
+ return fr_ldap_trunk_search(profile_ctx, &profile_ctx->query, request, profile_ctx->ttrunk,
+ base, scope, filter,
+ profile_ctx->expanded->attrs, serverctrls, NULL);
}
/** Search for and apply an LDAP profile
char const *dn, int scope, char const *filter, fr_ldap_map_exp_t const *expanded)
{
ldap_profile_ctx_t *profile_ctx;
- LDAPControl *serverctrls[] = { inst->profile.obj_sort_ctrl, NULL };
if (!dn || !*dn) return UNLANG_ACTION_CALCULATE_RESULT;
.applied = applied,
.dn = dn,
.expanded = expanded,
- .inst = inst
+ .inst = inst,
+ .ttrunk = ttrunk
};
if (ret) *ret = LDAP_RESULT_ERROR;
- if (unlang_function_push(request,
- NULL,
- ldap_map_profile_resume,
- ldap_map_profile_cancel, ~FR_SIGNAL_CANCEL,
- UNLANG_SUB_FRAME,
- profile_ctx) < 0) {
- talloc_free(profile_ctx);
- return UNLANG_ACTION_FAIL;
+ return ldap_profile_search_push(profile_ctx, request, dn, scope, filter);
+}
+
+/** Search for and apply a set of LDAP profiles
+ *
+ * With search_mode = bulk, a single search retrieves every profile object, matching
+ * entries by DN using the attribute configured (or detected) for the directory,
+ * and profiles are applied in result order.
+ * With search_mode = seq, one search is run per profile DN, applied in list order.
+ *
+ * @param[out] ret Where to write the result of the last query.
+ * @param[out] applied Incremented by the number of profile maps applied.
+ * @param[in] inst LDAP module instance.
+ * @param[in] request Current request.
+ * @param[in] ttrunk Trunk connection on which to run LDAP queries.
+ * @param[in] dn_list NULL terminated list of profile object DNs to apply,
+ * in application order (default profile first).
+ * Must contain at least one DN, and no empty strings.
+ * @param[in] filter to apply when looking up profiles.
+ * @param[in] expanded Structure containing a list of xlat
+ * expanded attribute names and mapping information.
+ * @return An unlang_action_t.
+ */
+unlang_action_t rlm_ldap_map_profiles(fr_ldap_result_code_t *ret, int *applied,
+ rlm_ldap_t const *inst, request_t *request, fr_ldap_thread_trunk_t *ttrunk,
+ char const * const *dn_list, char const *filter,
+ fr_ldap_map_exp_t const *expanded)
+{
+ ldap_profile_ctx_t *profile_ctx;
+
+ fr_assert(dn_list && *dn_list);
+
+ MEM(profile_ctx = talloc(unlang_interpret_frame_talloc_ctx(request), ldap_profile_ctx_t));
+ *profile_ctx = (ldap_profile_ctx_t) {
+ .ret = ret,
+ .applied = applied,
+ .expanded = expanded,
+ .inst = inst,
+ .ttrunk = ttrunk,
+ .filter = filter
+ };
+ if (ret) *ret = LDAP_RESULT_ERROR;
+
+ switch (inst->profile.search_mode) {
+ case LDAP_PROFILE_SEARCH_MODE_BULK:
+ {
+ char const *base, *dn_attr;
+
+ dn_attr = inst->dn_attr;
+ if (!dn_attr) dn_attr = ttrunk->directory->dn_attr;
+ fr_assert(dn_attr);
+
+ base = fr_ldap_directory_common_base_find(ttrunk->directory, dn_list);
+ if (!base) {
+ RWDEBUG("Retrieving profiles one at a time, no naming context contains every profile DN");
+ goto seq;
+ }
+
+ return ldap_profile_search_push(profile_ctx, request, base, LDAP_SCOPE_SUB,
+ fr_ldap_filter_afrom_dn_list(profile_ctx, dn_attr, filter, dn_list));
}
- return fr_ldap_trunk_search(profile_ctx, &profile_ctx->query, request, ttrunk, dn,
- scope, filter,
- expanded->attrs, serverctrls, NULL);
+ case LDAP_PROFILE_SEARCH_MODE_SEQ:
+ seq:
+ profile_ctx->dn = dn_list[0];
+ profile_ctx->next = dn_list + 1;
+
+ return ldap_profile_search_push(profile_ctx, request, profile_ctx->dn, inst->profile.obj_scope, filter);
+
+ case LDAP_PROFILE_SEARCH_MODE_AUTO:
+ fr_assert_msg(false, "search mode auto should've been resolved at startup");
+ break;
+ }
+
+ talloc_free(profile_ctx);
+ return UNLANG_ACTION_FAIL;
}
CALL_ENV_TERMINATOR
};
+static fr_table_num_sorted_t const profile_search_mode_table[] = {
+ { L("auto"), LDAP_PROFILE_SEARCH_MODE_AUTO },
+ { L("bulk"), LDAP_PROFILE_SEARCH_MODE_BULK },
+ { L("seq"), LDAP_PROFILE_SEARCH_MODE_SEQ }
+};
+static size_t profile_search_mode_table_len = NUM_ELEMENTS(profile_search_mode_table);
+
static conf_parser_t profile_config[] = {
{ FR_CONF_OFFSET("scope", rlm_ldap_t, profile.obj_scope), .dflt = "base",
.func = cf_table_parse_int, .uctx = &(cf_table_parse_ctx_t){ .table = fr_ldap_scope, .len = &fr_ldap_scope_len } },
+ { FR_CONF_OFFSET("search_mode", rlm_ldap_t, profile.search_mode), .dflt = "auto",
+ .func = cf_table_parse_int, .uctx = &(cf_table_parse_ctx_t){ .table = profile_search_mode_table, .len = &profile_search_mode_table_len } },
{ FR_CONF_OFFSET("attribute", rlm_ldap_t, profile.attr) },
{ FR_CONF_OFFSET("attribute_suspend", rlm_ldap_t, profile.attr_suspend) },
{ FR_CONF_OFFSET("check_attribute", rlm_ldap_t, profile.check_attr) },
}
FALL_THROUGH;
- case LDAP_AUTZ_DEFAULT_PROFILE:
- /*
- * Apply ONE user profile, or a default user profile.
- */
- if (call_env->default_profile.type == FR_TYPE_STRING) {
- REPEAT_MOD_AUTHORIZE_RESUME;
- ret = rlm_ldap_map_profile(NULL, NULL, inst, request, autz_ctx->ttrunk,
- call_env->default_profile.vb_strvalue,
- inst->profile.obj_scope, NULL, &autz_ctx->expanded);
- switch (ret) {
- case UNLANG_ACTION_FAIL:
- p_result->rcode = RLM_MODULE_FAIL;
- goto finish;
-
- case UNLANG_ACTION_PUSHED_CHILD:
- autz_ctx->status = LDAP_AUTZ_POST_DEFAULT_PROFILE;
- return UNLANG_ACTION_PUSHED_CHILD;
-
- default:
- break;
- }
- }
- FALL_THROUGH;
-
- case LDAP_AUTZ_POST_DEFAULT_PROFILE:
- /*
- * Did we jump back her after applying the default profile?
- */
- if (autz_ctx->status == LDAP_AUTZ_POST_DEFAULT_PROFILE) autz_ctx->rcode = RLM_MODULE_UPDATED;
+ case LDAP_AUTZ_PROFILES:
+ {
+ struct berval **values = NULL;
+ char const *profile_attr = NULL;
+ bool have_default = !fr_box_is_null(&call_env->default_profile);
+ int count;
/*
- * Apply a SET of user profiles.
+ * Which set of user profiles to apply depends on the
+ * user's access state. The default profile always applies.
*/
switch (autz_ctx->access_state) {
case LDAP_ACCESS_ALLOWED:
- if (inst->profile.attr) {
- int count;
-
- autz_ctx->profile_values = ldap_get_values_len(handle, autz_ctx->entry, inst->profile.attr);
- count = ldap_count_values_len(autz_ctx->profile_values);
- if (count > 0) {
- RDEBUG2("Processing %i profile(s) found in attribute \"%s\"", count, inst->profile.attr);
- if (RDEBUG_ENABLED3) {
- for (struct berval **bv_p = autz_ctx->profile_values; *bv_p; bv_p++) {
- RDEBUG3("Will evaluate profile with DN \"%pV\"", fr_box_strvalue_len((*bv_p)->bv_val, (*bv_p)->bv_len));
- }
- }
- } else {
- RDEBUG2("No profile(s) found in attribute \"%s\"", inst->profile.attr);
- }
- }
+ profile_attr = inst->profile.attr;
break;
case LDAP_ACCESS_SUSPENDED:
- if (inst->profile.attr_suspend) {
- int count;
-
- autz_ctx->profile_values = ldap_get_values_len(handle, autz_ctx->entry, inst->profile.attr_suspend);
- count = ldap_count_values_len(autz_ctx->profile_values);
- if (count > 0) {
- RDEBUG2("Processing %i suspension profile(s) found in attribute \"%s\"", count, inst->profile.attr_suspend);
- if (RDEBUG_ENABLED3) {
- for (struct berval **bv_p = autz_ctx->profile_values; *bv_p; bv_p++) {
- RDEBUG3("Will evaluate suspenension profile with DN \"%pV\"",
- fr_box_strvalue_len((*bv_p)->bv_val, (*bv_p)->bv_len));
- }
- }
- } else {
- RDEBUG2("No suspension profile(s) found in attribute \"%s\"", inst->profile.attr_suspend);
- }
- }
+ profile_attr = inst->profile.attr_suspend;
break;
case LDAP_ACCESS_DISALLOWED:
break;
}
+ if (profile_attr) {
+ values = ldap_get_values_len(handle, autz_ctx->entry, profile_attr);
+ count = ldap_count_values_len(values);
+ if (count > 0) {
+ RDEBUG2("Processing %i profile(s) found in attribute \"%s\"", count, profile_attr);
+ } else {
+ RDEBUG2("No profile(s) found in attribute \"%s\"", profile_attr);
+ }
+ } else {
+ count = 0;
+ }
- FALL_THROUGH;
+ if (!have_default && (count == 0)) break;
- case LDAP_AUTZ_USER_PROFILE:
/*
- * After each profile has been applied, execution will restart here.
- * Start by clearing the previously used value.
+ * Build the list of profile DNs to apply, the default
+ * profile first, then the profiles from the user object.
*/
- if (autz_ctx->profile_value) {
- TALLOC_FREE(autz_ctx->profile_value);
- autz_ctx->rcode = RLM_MODULE_UPDATED; /* We're back here after applying a profile successfully */
+ autz_ctx->profile_dn_list = fr_ldap_berval_to_string_list(autz_ctx, values, count, have_default ? 1 : 0);
+ if (have_default) autz_ctx->profile_dn_list[0] = call_env->default_profile.vb_strvalue;
+ if (values) ldap_value_free_len(values);
+
+ if (!autz_ctx->profile_dn_list[0]) break;
+
+ if (RDEBUG_ENABLED3) {
+ for (char const **dn_p = autz_ctx->profile_dn_list; *dn_p; dn_p++) {
+ RDEBUG3("Will evaluate profile with DN \"%s\"", *dn_p);
+ }
}
- if (autz_ctx->profile_values && autz_ctx->profile_values[autz_ctx->value_idx]) {
- autz_ctx->profile_value = fr_ldap_berval_to_string(autz_ctx, autz_ctx->profile_values[autz_ctx->value_idx++]);
- REPEAT_MOD_AUTHORIZE_RESUME;
- ret = rlm_ldap_map_profile(NULL, NULL, inst, request, autz_ctx->ttrunk, autz_ctx->profile_value,
- inst->profile.obj_scope, autz_ctx->call_env->profile_filter.vb_strvalue, &autz_ctx->expanded);
- switch (ret) {
- case UNLANG_ACTION_FAIL:
- p_result->rcode = RLM_MODULE_FAIL;
- goto finish;
+ REPEAT_MOD_AUTHORIZE_RESUME;
+ ret = rlm_ldap_map_profiles(NULL, &autz_ctx->profiles_applied, inst, request, autz_ctx->ttrunk,
+ autz_ctx->profile_dn_list, call_env->profile_filter.vb_strvalue,
+ &autz_ctx->expanded);
+ switch (ret) {
+ case UNLANG_ACTION_FAIL:
+ p_result->rcode = RLM_MODULE_FAIL;
+ goto finish;
- case UNLANG_ACTION_PUSHED_CHILD:
- autz_ctx->status = LDAP_AUTZ_USER_PROFILE;
- return UNLANG_ACTION_PUSHED_CHILD;
+ case UNLANG_ACTION_PUSHED_CHILD:
+ autz_ctx->status = LDAP_AUTZ_POST_PROFILES;
+ return UNLANG_ACTION_PUSHED_CHILD;
- default:
- break;
- }
+ default:
+ break;
}
+ }
+ FALL_THROUGH;
+
+ case LDAP_AUTZ_POST_PROFILES:
+ if (autz_ctx->profiles_applied > 0) autz_ctx->rcode = RLM_MODULE_UPDATED;
break;
}
static int autz_ctx_free(ldap_autz_ctx_t *autz_ctx)
{
talloc_free(autz_ctx->expanded.ctx);
- if (autz_ctx->profile_values) ldap_value_free_len(autz_ctx->profile_values);
return 0;
}
SSS_CONTROL_BUILD(user)
SSS_CONTROL_BUILD(profile)
+ /*
+ * Bulk retrieval matches profile objects by their exact DN,
+ * so subtree (non-base scope) semantics cannot be preserved.
+ */
+ switch (inst->profile.search_mode) {
+ case LDAP_PROFILE_SEARCH_MODE_AUTO:
+ if (inst->profile.obj_sort_ctrl && (inst->profile.obj_scope == LDAP_SCOPE_BASE)) {
+ inst->profile.search_mode = LDAP_PROFILE_SEARCH_MODE_BULK;
+ } else {
+ inst->profile.search_mode = LDAP_PROFILE_SEARCH_MODE_SEQ;
+ }
+ break;
+
+ case LDAP_PROFILE_SEARCH_MODE_BULK:
+ if (inst->profile.obj_scope != LDAP_SCOPE_BASE) {
+ cf_log_err(conf, "'profile.search_mode = bulk' requires 'profile.scope = base'");
+ return -1;
+ }
+ if (!inst->profile.obj_sort_ctrl) {
+ cf_log_err(conf, "'profile.search_mode = bulk' requires 'profile.sort_by', "
+ "else profile evaluation order is non-deterministic");
+ return -1;
+ }
+ break;
+
+ case LDAP_PROFILE_SEARCH_MODE_SEQ:
+ break;
+ }
+
if (inst->handle_config.tls_require_cert_str) {
/*
* Convert cert strictness to enumerated constants
*/
inst->handle_config.tls_require_cert = fr_table_value_by_str(fr_ldap_tls_require_cert,
- inst->handle_config.tls_require_cert_str, -1);
+ inst->handle_config.tls_require_cert_str, -1);
if (inst->handle_config.tls_require_cert < 0) {
cf_log_err(conf, "Invalid 'tls.require_cert' value \"%s\", expected 'never', "
- "'demand', 'allow', 'try' or 'hard'", inst->handle_config.tls_require_cert_str);
+ "'demand', 'allow', 'try' or 'hard'", inst->handle_config.tls_require_cert_str);
return -1;
}
}