TARGET := $(TARGETNAME)$(L)
endif
-SOURCES := base.c bind.c connection.c control.c directory.c edir.c map.c referral.c start_tls.c state.c util.c @SASL@
+SOURCES := base.c bind.c conf.c connection.c control.c directory.c edir.c filter.c map.c referral.c start_tls.c state.c util.c @SASL@
SRC_CFLAGS := @mod_cflags@
TGT_LDLIBS := @mod_ldflags@
#include <freeradius-devel/server/global_lib.h>
#include <freeradius-devel/server/map.h>
#include <freeradius-devel/server/trunk.h>
+#include <freeradius-devel/util/dlist.h>
#define LDAP_DEPRECATED 0 /* Quiet warnings about LDAP_DEPRECATED not being defined */
//!< persistent search.
#define LDAP_SERVER_SHOW_DELETED_OID "1.2.840.113556.1.4.417" //!< OID of Active Directory control which
//!< enables searching for deleted objects.
+#define LDAP_MATCHING_RULE_BIT_AND "1.2.840.113556.1.4.803" //!< OID of bit-wise AND LDAP match rule
+#define LDAP_MATCHING_RULE_BIT_OR "1.2.840.113556.1.4.804" //!< OID of bit-wise OR LDAP match rule
typedef enum {
LDAP_EXT_UNSUPPORTED, //!< Unsupported extension.
bool cleartext_password; //!< Whether the server will return the user's plaintext
///< password.
- fr_ldap_sync_type_t sync_type; //! <What kind of LDAP sync this directory supports.
+ fr_ldap_sync_type_t sync_type; //!< What kind of LDAP sync this directory supports.
+
+ char const **naming_contexts; //!< Databases served by this directory.
} fr_ldap_directory_t;
/** Connection configuration
fr_ldap_result_code_t ret; //!< Return code of bind operation.
} fr_ldap_bind_auth_ctx_t;
+typedef struct ldap_filter_s ldap_filter_t;
+
+/** Types of parsed LDAP filter nodes
+ */
+typedef enum {
+ LDAP_FILTER_NODE = 0, //!< The filter node is an individual one
+ //!< to be evaluated against an attribute.
+ LDAP_FILTER_GROUP //!< The filter node is a parent of a group
+ //!< which will be combined using a logical operator.
+} ldap_filter_type_t;
+
+/** Logical operators for use in LDAP filters
+ */
+typedef enum {
+ LDAP_FILTER_LOGIC_AND = 1,
+ LDAP_FILTER_LOGIC_OR,
+ LDAP_FILTER_LOGIC_NOT
+} ldap_filter_logic_t;
+
+/** Operators for use in LDAP filters
+ */
+typedef enum {
+ LDAP_FILTER_OP_UNSET = 0, //!< Attribute not set yet
+ LDAP_FILTER_OP_EQ, //!< Attribute equals value
+ LDAP_FILTER_OP_SUBSTR, //!< Attribute matches string with wildcards
+ LDAP_FILTER_OP_PRESENT, //!< Attribute present
+ LDAP_FILTER_OP_GE, //!< Attribute greater than or equal to value
+ LDAP_FILTER_OP_LE, //!< Attribute less than or equal to value
+ LDAP_FILTER_OP_BIT_AND, //!< Bitwise AND comparison
+ LDAP_FILTER_OP_BIT_OR //!< Bitwise OR comparison
+} ldap_filter_op_t;
+
+/** Structure to hold parsed details of LDAP filters
+ */
+struct ldap_filter_s {
+ fr_dlist_t entry; //!< Entry in the list of filter nodes.
+ ldap_filter_type_t filter_type; //!< Type of this filter node.
+ char *orig; //!< Text representation of filter for debug messages,
+ union {
+ struct {
+ ldap_filter_logic_t logic_op; //!< Logical operator for this group.
+ fr_dlist_head_t children; //!< List of child nodes in this group.
+ };
+ struct {
+ char *attr; //!< Attribute for the filter node.
+ ldap_filter_op_t op; //!< Operator to be used for comparison.
+ fr_value_box_t *value; //!< Value to compare with.
+ };
+ };
+};
+
/** Codes returned by fr_ldap internal functions
*
*/
bool is_async);
/*
- * ldap.c - Wrappers arounds OpenLDAP functions.
+ * base.c - Wrappers arounds OpenLDAP functions.
*/
void fr_ldap_timeout_debug(request_t *request, fr_ldap_connection_t const *conn,
fr_time_delta_t timeout, char const *prefix);
"objectClass", \
"orcldirectoryversion", \
"supportedControl", \
+ "namingContexts", \
NULL }
int fr_ldap_directory_result_parse(fr_ldap_directory_t *directory, LDAP *handle,
int fr_ldap_parse_url_extensions(LDAPControl **sss, size_t sss_len, char *extensions[]);
+int fr_ldap_attrs_check(char const **attrs, char const *attr);
+
+int fr_ldap_server_url_check(fr_ldap_config_t *handle_config, char const *server, CONF_SECTION const *cs);
+
+int fr_ldap_server_config_check(fr_ldap_config_t *handle_config, char const *server, CONF_SECTION *cs);
+
/*
* referral.c - Handle LDAP referrals
*/
int fr_ldap_referral_follow(fr_ldap_thread_t *thread, request_t *request, fr_ldap_query_t *query);
int fr_ldap_referral_next(fr_ldap_thread_t *thread, request_t *request, fr_ldap_query_t *query);
+
+/*
+ * filter.c - Basic filter parsing and filtering
+ */
+typedef int (*filter_attr_check_t)(char const *attr, void *uctx);
+
+fr_slen_t fr_ldap_filter_parse(TALLOC_CTX *ctx, fr_dlist_head_t **root, fr_sbuff_t *filter,
+ filter_attr_check_t attr_check, void *uctx);
+
+bool fr_ldap_filter_eval(fr_dlist_head_t *root, fr_ldap_connection_t *conn, LDAPMessage *msg);
--- /dev/null
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file ldap/conf.c
+ * @brief Configuration parsing for LDAP server connections.
+ *
+ * @copyright 2022 The FreeRADIUS server project
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/ldap/base.h>
+#include <freeradius-devel/ldap/conf.h>
+
+CONF_PARSER const fr_ldap_sasl_mech_static[] = {
+ { FR_CONF_OFFSET("mech", FR_TYPE_STRING | FR_TYPE_NOT_EMPTY, fr_ldap_sasl_t, mech) },
+ { FR_CONF_OFFSET("proxy", FR_TYPE_STRING, fr_ldap_sasl_t, proxy) },
+ { FR_CONF_OFFSET("realm", FR_TYPE_STRING, fr_ldap_sasl_t, realm) },
+ CONF_PARSER_TERMINATOR
+};
+
+/*
+ * TLS Configuration
+ */
+CONF_PARSER const fr_ldap_tls_config[] = {
+ /*
+ * Deprecated attributes
+ */
+ { FR_CONF_OFFSET("ca_file", FR_TYPE_FILE_INPUT, fr_ldap_config_t, tls_ca_file) },
+
+ { FR_CONF_OFFSET("ca_path", FR_TYPE_FILE_INPUT, fr_ldap_config_t, tls_ca_path) },
+
+ { FR_CONF_OFFSET("certificate_file", FR_TYPE_FILE_INPUT, fr_ldap_config_t, tls_certificate_file) },
+
+ { FR_CONF_OFFSET("private_key_file", FR_TYPE_FILE_INPUT, fr_ldap_config_t, tls_private_key_file) },
+
+ /*
+ * LDAP Specific TLS attributes
+ */
+ { FR_CONF_OFFSET("start_tls", FR_TYPE_BOOL, fr_ldap_config_t, start_tls), .dflt = "no" },
+
+ { FR_CONF_OFFSET("require_cert", FR_TYPE_STRING, fr_ldap_config_t, tls_require_cert_str) },
+
+ { FR_CONF_OFFSET("tls_min_version", FR_TYPE_STRING, fr_ldap_config_t, tls_min_version_str) },
+
+ CONF_PARSER_TERMINATOR
+};
+
+/*
+ * Various options that don't belong in the main configuration.
+ *
+ * Note that these overlap a bit with the connection pool code!
+ */
+CONF_PARSER const fr_ldap_option_config[] = {
+ /*
+ * Pool config items
+ */
+ { FR_CONF_OFFSET("chase_referrals", FR_TYPE_BOOL, fr_ldap_config_t, chase_referrals) },
+
+ { FR_CONF_OFFSET("use_referral_credentials", FR_TYPE_BOOL, fr_ldap_config_t, use_referral_credentials), .dflt = "no" },
+
+ { FR_CONF_OFFSET("referral_depth", FR_TYPE_UINT16, fr_ldap_config_t, referral_depth), .dflt = "5" },
+
+ { FR_CONF_OFFSET("rebind", FR_TYPE_BOOL, fr_ldap_config_t, rebind) },
+
+ { FR_CONF_OFFSET("sasl_secprops", FR_TYPE_STRING, fr_ldap_config_t, sasl_secprops) },
+
+ /*
+ * We use this config option to populate libldap's LDAP_OPT_NETWORK_TIMEOUT -
+ * timeout on network activity - specifically libldap's initial call to "connect"
+ * Must be non-zero for async connections to start correctly.
+ */
+ { FR_CONF_OFFSET("net_timeout", FR_TYPE_TIME_DELTA, fr_ldap_config_t, net_timeout), .dflt = "10" },
+
+ { FR_CONF_OFFSET("idle", FR_TYPE_TIME_DELTA, fr_ldap_config_t, keepalive_idle), .dflt = "60" },
+
+ { FR_CONF_OFFSET("probes", FR_TYPE_UINT32, fr_ldap_config_t, keepalive_probes), .dflt = "3" },
+
+ { FR_CONF_OFFSET("interval", FR_TYPE_TIME_DELTA, fr_ldap_config_t, keepalive_interval), .dflt = "30" },
+
+ { FR_CONF_OFFSET("dereference", FR_TYPE_STRING, fr_ldap_config_t, dereference_str) },
+
+ /* allow server unlimited time for search (server-side limit) */
+ { FR_CONF_OFFSET("srv_timelimit", FR_TYPE_TIME_DELTA, fr_ldap_config_t, srv_timelimit), .dflt = "20" },
+
+ /*
+ * Instance config items
+ */
+ /* timeout for search results */
+ { FR_CONF_OFFSET("res_timeout", FR_TYPE_TIME_DELTA, fr_ldap_config_t, res_timeout), .dflt = "20" },
+
+ { FR_CONF_OFFSET("idle_timeout", FR_TYPE_TIME_DELTA, fr_ldap_config_t, idle_timeout), .dflt = "300" },
+
+ { FR_CONF_OFFSET("reconnection_delay", FR_TYPE_TIME_DELTA, fr_ldap_config_t, reconnection_delay), .dflt = "10" },
+
+ CONF_PARSER_TERMINATOR
+};
+
--- /dev/null
+#pragma once
+/**
+ * $Id$
+ * @file lib/ldap/conf.h
+ * @brief Configuration parsing for LDAP server connections.
+ *
+ * @copyright 2022 The FreeRADIUS Server Project.
+ */
+
+#include <freeradius-devel/ldap/base.h>
+
+extern CONF_PARSER const fr_ldap_sasl_mech_static[];
+extern CONF_PARSER const fr_ldap_tls_config[];
+extern CONF_PARSER const fr_ldap_option_config[];
+
+/*
+ * Macro for including common LDAP configuration items
+ */
+#define FR_LDAP_COMMON_CONF(_conf) { FR_CONF_OFFSET("port", FR_TYPE_UINT16, _conf, handle_config.port) }, \
+ { FR_CONF_OFFSET("identity", FR_TYPE_STRING, _conf, handle_config.admin_identity) }, \
+ { FR_CONF_OFFSET("password", FR_TYPE_STRING | FR_TYPE_SECRET, _conf, handle_config.admin_password) }, \
+ { FR_CONF_OFFSET("sasl", FR_TYPE_SUBSECTION, _conf, handle_config.admin_sasl), .subcs = (void const *) fr_ldap_sasl_mech_static }, \
+ { FR_CONF_OFFSET("options", FR_TYPE_SUBSECTION, _conf, handle_config), .subcs = (void const *) fr_ldap_option_config }, \
+ { FR_CONF_OFFSET("tls", FR_TYPE_SUBSECTION, _conf, handle_config), .subcs = (void const *) fr_ldap_tls_config }
WARN("No supportedControl returned by LDAP server");
}
+ /*
+ * Extract naming contexts
+ */
+ values = ldap_get_values_len(handle, entry, "namingContexts");
+ if (!values) return 0;
+
+ num = ldap_count_values_len(values);
+ directory->naming_contexts = talloc_array(directory, char const *, num);
+ for (i = 0; i < num; i++) {
+ directory->naming_contexts[i] = fr_ldap_berval_to_string(directory, values[i]);
+ }
+ ldap_value_free_len(values);
+
return 0;
}
--- /dev/null
+/*
+ * This program is is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file lib/ldap/filter.c
+ * @brief Functions to handle basic LDAP filter parsing and filtering
+ *
+ * @copyright 2022 Network RADIUS SARL (legal@networkradius.com)
+ */
+
+#include <freeradius-devel/ldap/base.h>
+
+static fr_table_num_sorted_t const ldap_filter_op_table[] = {
+ { L("<="), LDAP_FILTER_OP_LE },
+ { L("="), LDAP_FILTER_OP_EQ },
+ { L(">="), LDAP_FILTER_OP_GE }
+};
+static size_t ldap_filter_op_table_len = NUM_ELEMENTS(ldap_filter_op_table);
+
+static bool const fr_ldap_attr_allowed_chars[UINT8_MAX + 1] = {
+ ['-'] = true,
+ SBUFF_CHAR_CLASS_ALPHA_NUM
+};
+
+#define FILTER_ATTR_MAX_LEN 256
+#define FILTER_VALUE_MAX_LEN 256
+
+static fr_slen_t ldap_filter_parse_node(ldap_filter_t *node, fr_sbuff_t *sbuff, int depth,
+ filter_attr_check_t attr_check, void *uctx);
+
+/** Parse LDAP filter logic group
+ *
+ * @param[in,out] node to populate with parsed filter.
+ * @param[in] sbuff pointing to filter to parse.
+ * @param[in] depth to indent debug output, indicating nesting of groups.
+ * @param[in] attr_check callback to check if required attributes are in the query.
+ * @param[in] uctx passed to attribute check callback.
+ * @return
+ * - number of bytes parsed on success
+ * - < 0 on error
+ */
+static fr_slen_t ldap_filter_parse_logic(ldap_filter_t *node, fr_sbuff_t *sbuff, int depth,
+ filter_attr_check_t attr_check, void *uctx)
+{
+ ldap_filter_t *child_node;
+ fr_slen_t ret = 0;
+ fr_slen_t parsed = 0;
+
+ switch(*fr_sbuff_current(sbuff)) {
+ case '&':
+ node->logic_op = LDAP_FILTER_LOGIC_AND;
+ node->orig = talloc_typed_strdup(node, "&");
+ break;
+
+ case '|':
+ node->logic_op = LDAP_FILTER_LOGIC_OR;
+ node->orig = talloc_typed_strdup(node, "|");
+ break;
+
+ case '!':
+ node->logic_op = LDAP_FILTER_LOGIC_NOT;
+ node->orig = talloc_typed_strdup(node, "!");
+ break;
+ }
+ parsed += fr_sbuff_advance(sbuff, 1);
+
+ DEBUG3("%*sCreating LDAP filter group %s", depth, "", node->orig);
+ node->filter_type = LDAP_FILTER_GROUP;
+ fr_dlist_init(&node->children, ldap_filter_t, entry);
+ MEM(child_node = talloc_zero(node, ldap_filter_t));
+ fr_dlist_insert_head(&node->children, child_node);
+
+ depth += 2;
+ ret = ldap_filter_parse_node(child_node, sbuff, depth, attr_check, uctx);
+ if (ret < 0) return ret;
+ parsed += ret;
+
+ /*
+ * Look for sibling nodes to the child just processed
+ */
+ while (fr_sbuff_is_char(sbuff, '(')) {
+ if (node->logic_op == LDAP_FILTER_LOGIC_NOT) {
+ fr_strerror_const("'!' operator can only apply to one filter");
+ return fr_sbuff_error(sbuff);
+ }
+ MEM(child_node = talloc_zero(node, ldap_filter_t));
+ fr_dlist_insert_tail(&node->children, child_node);
+ ret = ldap_filter_parse_node(child_node, sbuff, depth, attr_check, uctx);
+ if (ret < 0) return ret;
+ parsed += ret;
+ }
+
+ return parsed;
+}
+
+/** Parse individual LDAP filter
+ *
+ * @param[in,out] node to populate with parsed filter.
+ * @param[in] sbuff pointing to filter to parse.
+ * @param[in] depth to indent debug output, indicating nesting of groups.
+ * @param[in] attr_check callback to check if required attributes are in the query.
+ * @param[in] uctx passed to attribute check callback.
+ * @return
+ * - number of bytes parsed on success
+ * - < 0 on error
+ */
+static fr_slen_t ldap_filter_parse_filter(ldap_filter_t *node, fr_sbuff_t *sbuff, int depth,
+ filter_attr_check_t attr_check, void *uctx)
+{
+ char attr_buffer[FILTER_ATTR_MAX_LEN], val_buffer[FILTER_VALUE_MAX_LEN];
+ fr_sbuff_t attr_sbuff = FR_SBUFF_IN(attr_buffer, FILTER_ATTR_MAX_LEN);
+ fr_sbuff_t val_sbuff = FR_SBUFF_IN(val_buffer, FILTER_VALUE_MAX_LEN);
+ size_t len;
+ ssize_t slen;
+ ldap_filter_op_t op;
+ fr_sbuff_marker_t marker;
+
+ fr_sbuff_marker(&marker, sbuff);
+
+ /*
+ * Extract the attribute name, blanking the buffer first.
+ */
+ memset(attr_buffer, 0, FILTER_ATTR_MAX_LEN);
+ len = fr_sbuff_out_bstrncpy_allowed(&attr_sbuff, sbuff, FILTER_ATTR_MAX_LEN - 1, fr_ldap_attr_allowed_chars);
+ if (len == 0) {
+ fr_strerror_const("Missing attribute name");
+ return fr_sbuff_error(sbuff);
+ }
+
+ MEM(node->attr = talloc_zero_array(node, char, len+1));
+ memcpy(node->attr, attr_buffer, len);
+
+ /*
+ * Check for the attribute needed for the filter using the
+ * provided callback.
+ */
+ if (attr_check) attr_check(node->attr, uctx);
+
+ /*
+ * If the attribute name is followed by ':' there is an
+ * extended match rule. We only support two of them.
+ */
+ if (fr_sbuff_next_if_char(sbuff, ':')) {
+ if (fr_sbuff_adv_past_str_literal(sbuff, LDAP_MATCHING_RULE_BIT_AND)) {
+ node->op = LDAP_FILTER_OP_BIT_AND;
+ goto found_op;
+ }
+ if (fr_sbuff_adv_past_str_literal(sbuff, LDAP_MATCHING_RULE_BIT_OR)) {
+ node->op = LDAP_FILTER_OP_BIT_OR;
+ goto found_op;
+ }
+
+ fr_strerror_const("Unsupported extended match rule");
+ return fr_sbuff_error(sbuff);
+
+ found_op:
+ if(!(fr_sbuff_next_if_char(sbuff, ':'))) {
+ fr_strerror_const("Missing ':' after extended match rule");
+ return fr_sbuff_error(sbuff);
+ }
+ }
+
+ fr_sbuff_out_by_longest_prefix(&slen, &op, ldap_filter_op_table, sbuff, 0);
+
+ switch(op) {
+ case LDAP_FILTER_OP_EQ:
+ if (node->op == LDAP_FILTER_OP_UNSET) node->op = op;
+ break;
+
+ case LDAP_FILTER_OP_LE:
+ case LDAP_FILTER_OP_GE:
+ node->op = op;
+ break;
+
+ default:
+ fr_strerror_const("Incorrect operator");
+ return fr_sbuff_error(sbuff);
+ }
+
+ if (((node->op == LDAP_FILTER_OP_BIT_AND) || (node->op == LDAP_FILTER_OP_BIT_OR)) &&
+ (op != LDAP_FILTER_OP_EQ)) {
+ fr_strerror_const("Extended match rule only valid with '=' operator");
+ return fr_sbuff_error(sbuff);
+ }
+
+ /*
+ * Capture everything up to the next ')' as the value, blanking the buffer first.
+ */
+ memset(val_buffer, 0, FILTER_VALUE_MAX_LEN);
+ len = fr_sbuff_out_bstrncpy_until(&val_sbuff, sbuff, FILTER_VALUE_MAX_LEN - 1, &FR_SBUFF_TERM(")"), NULL);
+
+ if (len == 0) {
+ fr_strerror_const("Missing filter value");
+ return fr_sbuff_error(sbuff);
+ }
+
+ /*
+ * An equality test with a value of '*' is a present test
+ */
+ if ((len == 1) && (*val_buffer == '*') && (node->op == LDAP_FILTER_OP_EQ)) node->op = LDAP_FILTER_OP_PRESENT;
+
+ /*
+ * Equality tests with '*' in the value are substring matches
+ */
+ fr_sbuff_set_to_start(&val_sbuff);
+ if ((node->op == LDAP_FILTER_OP_EQ) && (fr_sbuff_adv_to_chr(&val_sbuff, SIZE_MAX, '*'))) {
+ node->op = LDAP_FILTER_OP_SUBSTR;
+ }
+
+ MEM(node->value = fr_value_box_alloc_null(node));
+
+ switch (node->op) {
+ case LDAP_FILTER_OP_EQ:
+ case LDAP_FILTER_OP_PRESENT:
+ case LDAP_FILTER_OP_SUBSTR:
+ if (fr_value_box_bstrndup(node, node->value, NULL, val_buffer, len, false) < 0) {
+ fr_strerror_const("Failed parsing value for filter");
+ return fr_sbuff_error(sbuff);
+ }
+ break;
+
+ /*
+ * Since we don't have the LDAP schema, we make an assumption that <=, >= and
+ * bitwise operators are going to be used with numeric attributes
+ */
+ case LDAP_FILTER_OP_GE:
+ case LDAP_FILTER_OP_LE:
+ case LDAP_FILTER_OP_BIT_AND:
+ case LDAP_FILTER_OP_BIT_OR:
+ if (fr_value_box_from_str(node, node->value, FR_TYPE_UINT32, NULL,
+ val_buffer, len, NULL, false) < 0) {
+ fr_strerror_const("Failed parsing value for filter");
+ return fr_sbuff_error(sbuff);
+ }
+ break;
+
+ /*
+ * Operator should not be unset at the end of a filter
+ */
+ case LDAP_FILTER_OP_UNSET:
+ fr_assert(0);
+ break;
+ }
+
+ /*
+ * Take a copy of the original filter for debug output
+ */
+ MEM(node->orig = talloc_zero_array(node, char, fr_sbuff_diff(sbuff, &marker) + 1));
+ memcpy(node->orig, fr_sbuff_current(&marker), fr_sbuff_diff(sbuff, &marker));
+ DEBUG3("%*sParsed LDAP filter (%s)", depth, "", node->orig);
+
+ return fr_sbuff_diff(sbuff, &marker);
+}
+
+/** Parse individual LDAP filter nodes
+ *
+ * A node can either be a group of nodes joined with a logical operator
+ * or an individual filter.
+ *
+ * @param[in,out] node to populate with parsed filter.
+ * @param[in] sbuff pointing to filter to parse.
+ * @param[in] depth to indent debug output, indicating nesting of groups.
+ * @param[in] attr_check callback to check if required attributes are in the query.
+ * @param[in] uctx passed to attribute check callback.
+ * @return
+ * - number of bytes parsed on success
+ * - < 0 on error
+ */
+static fr_slen_t ldap_filter_parse_node(ldap_filter_t *node, fr_sbuff_t *sbuff, int depth,
+ filter_attr_check_t attr_check, void *uctx)
+{
+ fr_sbuff_marker_t marker;
+ fr_slen_t ret;
+ fr_slen_t parsed = 0;
+
+ static bool const logical_op_chars[UINT8_MAX +1] = {
+ ['!'] = true, ['&'] = true, ['|'] = true,
+ };
+
+ if (!fr_sbuff_next_if_char(sbuff, '(')) {
+ fr_strerror_const("Missing '('");
+ return fr_sbuff_error(sbuff);
+ }
+
+ /*
+ * Firstly, look for the characters which indicate the start of a group of filters
+ * to be combined with a logical operator.
+ */
+ fr_sbuff_marker(&marker, sbuff);
+ if (fr_sbuff_adv_past_allowed(sbuff, 1, logical_op_chars, NULL)) {
+ fr_sbuff_set(sbuff, &marker);
+ ret = ldap_filter_parse_logic(node, sbuff, depth, attr_check, uctx);
+ } else {
+ ret = ldap_filter_parse_filter(node, sbuff, depth, attr_check, uctx);
+ }
+
+ if (ret < 0) return ret;
+ parsed += ret;
+
+ if (!fr_sbuff_next_if_char(sbuff, ')')) {
+ fr_strerror_const("Missing ')'");
+ return fr_sbuff_error(sbuff);
+ }
+ parsed ++;
+
+ /*
+ * If we're at the very top level we should be at the end
+ * of the buffer
+ */
+ if ((depth == 0) && (fr_sbuff_extend(sbuff))) {
+ fr_strerror_const("Extra characters at the end of LDAP filter");
+ return fr_sbuff_error(sbuff);
+ }
+
+ return parsed;
+}
+
+/** Parse an LDAP filter into its component nodes
+ *
+ * @param[in] ctx to allocate nodes in.
+ * @param[in,out] root where to allocate the root of the parsed filter.
+ * @param[in] filter to parse.
+ * @param[in] attr_check callback to check if required attributes are in the query.
+ * @param[in] uctx passed to attribute check callback.
+ * @return
+ * - number of bytes parsed on success
+ * < 0 on failure
+ */
+fr_slen_t fr_ldap_filter_parse(TALLOC_CTX *ctx, fr_dlist_head_t **root, fr_sbuff_t *filter,
+ filter_attr_check_t attr_check, void *uctx)
+{
+ ldap_filter_t *node;
+ fr_slen_t ret;
+
+ MEM(*root = talloc_zero(ctx, fr_dlist_head_t));
+ fr_dlist_init(*root, ldap_filter_t, entry);
+
+ MEM(node = talloc_zero(*root, ldap_filter_t));
+ fr_dlist_insert_head(*root, node);
+
+ ret = ldap_filter_parse_node(node, filter, 0, attr_check, uctx);
+ if (ret < 0) {
+ talloc_free(*root);
+ *root = NULL;
+ return ret;
+ }
+
+ return ret;
+}
+
+static bool ldap_filter_node_eval(ldap_filter_t *node, fr_ldap_connection_t *conn, LDAPMessage *msg, int depth);
+
+/** Evaluate a group of LDAP filters
+ *
+ * Groups have a logical operator of &, | or !
+ *
+ * @param[in] group to evaluate.
+ * @param[in] conn LDAP connection the message being filtered was returned on
+ * @param[in] msg to filter
+ * @param[in] depth to indent debug messages, reflecting group nesting
+ * @return true or false result of the group evaluation
+ */
+static bool ldap_filter_group_eval(ldap_filter_t *group, fr_ldap_connection_t *conn, LDAPMessage *msg, int depth)
+{
+ ldap_filter_t *node = NULL;
+ bool filter_state = false;
+
+ DEBUG3("%*sEvaluating LDAP filter group %s", depth, "", group->orig);
+ depth += 2;
+ while ((node = fr_dlist_next(&group->children, node))) {
+ switch (node->filter_type) {
+ case LDAP_FILTER_GROUP:
+ filter_state = ldap_filter_group_eval(node, conn, msg, depth);
+ break;
+ case LDAP_FILTER_NODE:
+ filter_state = ldap_filter_node_eval(node, conn, msg, depth);
+ break;
+ }
+
+ /*
+ * Short circuit the group depending on the logical operator
+ * and the return state of the last node
+ */
+ if (((group->logic_op == LDAP_FILTER_LOGIC_OR) && filter_state) ||
+ ((group->logic_op == LDAP_FILTER_LOGIC_AND) && !filter_state)) {
+ break;
+ }
+ }
+
+ filter_state = (group->logic_op == LDAP_FILTER_LOGIC_NOT ? !filter_state : filter_state);
+
+ depth -= 2;
+ DEBUG3("%*sLDAP filter group %s results in %s", depth, "", group->orig, (filter_state ? "TRUE" : "FALSE"));
+ return filter_state;
+}
+
+#define DEBUG_LDAP_ATTR_VAL if (DEBUG_ENABLED3) { \
+ fr_value_box_t value_box; \
+ fr_ldap_berval_to_value_str_shallow(&value_box, values[i]); \
+ DEBUG3("%*s Evaluating attribute \"%s\", value \"%pV\"", depth, "", node->attr, &value_box); \
+}
+
+/** Evaluate a single LDAP filter node
+ *
+ * @param[in] node to evaluate.
+ * @param[in] conn LDAP connection the message being filtered was returned on.
+ * @param[in] msg to filter.
+ * @param[in] depth to indent debug messages, reflecting group nesting.
+ * @return true or false result of the node evaluation.
+ */
+static bool ldap_filter_node_eval(ldap_filter_t *node, fr_ldap_connection_t *conn, LDAPMessage *msg, int depth)
+{
+ struct berval **values;
+ int count, i;
+ bool filter_state = false;
+
+ switch (node->filter_type) {
+ case LDAP_FILTER_GROUP:
+ return ldap_filter_group_eval(node, conn, msg, depth);
+
+ case LDAP_FILTER_NODE:
+ DEBUG3("%*sEvaluating LDAP filter (%s)", depth, "", node->orig);
+ values = ldap_get_values_len(conn->handle, msg, node->attr);
+ count = ldap_count_values_len(values);
+
+ switch (node->op) {
+ case LDAP_FILTER_OP_PRESENT:
+ filter_state = (count > 0 ? true : false);
+ break;
+
+ case LDAP_FILTER_OP_EQ:
+ for (i = 0; i < count; i++) {
+ DEBUG_LDAP_ATTR_VAL
+ if ((node->value->length == values[i]->bv_len) &&
+ (strncasecmp(values[i]->bv_val, node->value->vb_strvalue, values[i]->bv_len) == 0)) {
+ filter_state = true;
+ break;
+ }
+ }
+ break;
+
+ /*
+ * LDAP filters only use one wildcard character '*' for zero or more
+ * character matches.
+ */
+ case LDAP_FILTER_OP_SUBSTR:
+ {
+ char const *v, *t, *v_end, *t_end;
+ bool skip;
+
+ /*
+ * Point t_end at the final character of the filter value
+ * - not the NULL - so we can check for trailing '*'
+ */
+ t_end = node->value->vb_strvalue + node->value->length - 1;
+
+ for (i = 0; i < count; i++) {
+ DEBUG_LDAP_ATTR_VAL
+ t = node->value->vb_strvalue;
+ v = values[i]->bv_val;
+ v_end = values[i]->bv_val + values[i]->bv_len - 1;
+ skip = false;
+
+ /*
+ * Walk the value (v) and test (t), comparing until
+ * there is a mis-match or the end of one is reached.
+ */
+ while ((v <= v_end) && (t <= t_end)) {
+ /*
+ * If a wildcard is found in the test,
+ * indicate that we can skip non-matching
+ * characters in the value
+ */
+ if (*t == '*'){
+ skip = true;
+ t++;
+ continue;
+ }
+ if (skip) {
+ while ((tolower(*t) != tolower(*v)) && (v <= v_end)) v++;
+ }
+ if (tolower(*t) != tolower(*v)) break;
+ skip = false;
+ t++;
+ v++;
+ }
+
+ /*
+ * If we've got to the end of both the test and value,
+ * or we've used all of the test and the last character is '*'
+ * then we've matched the pattern.
+ */
+ if (((v > v_end) && (t > t_end)) || ((t >= t_end) && (*t_end == '*'))) {
+ filter_state = true;
+ break;
+ }
+ }
+ }
+ break;
+
+ /*
+ * For >=, <= and bitwise operators, we assume numeric values
+ */
+ case LDAP_FILTER_OP_GE:
+ case LDAP_FILTER_OP_LE:
+ case LDAP_FILTER_OP_BIT_AND:
+ case LDAP_FILTER_OP_BIT_OR:
+ {
+ char buffer[11]; /* Max uint32_t + 1 */
+ uint32_t value;
+ for (i = 0; i < count; i++) {
+ DEBUG_LDAP_ATTR_VAL
+ /*
+ * String too long for max uint32
+ */
+ if (values[i]->bv_len > 10) continue;
+
+ /*
+ * bv_val is not NULL terminated - so copy to a
+ * NULL terminated string before parsing.
+ */
+ memcpy(buffer, values[i]->bv_val, values[i]->bv_len);
+ buffer[values[i]->bv_len] = '\0';
+
+ value = (uint32_t)strtol(buffer, NULL, 10);
+ switch (node->op) {
+ case LDAP_FILTER_OP_GE:
+ if (value >= node->value->vb_uint32) filter_state = true;
+ break;
+ case LDAP_FILTER_OP_LE:
+ if (value <= node->value->vb_uint32) filter_state = true;
+ break;
+ case LDAP_FILTER_OP_BIT_AND:
+ if (value & node->value->vb_uint32) filter_state = true;
+ break;
+ case LDAP_FILTER_OP_BIT_OR:
+ if (value | node->value->vb_uint32) filter_state = true;
+ break;
+ default:
+ fr_assert(0);
+ break;
+ }
+ if (filter_state) break;
+ }
+ }
+ break;
+
+ default:
+ fr_assert(0);
+ break;
+
+ }
+
+ ldap_value_free_len(values);
+ }
+
+ DEBUG3("%*sLDAP filter returns %s", depth, "", (filter_state ? "TRUE" : "FALSE"));
+
+ return filter_state;
+}
+
+/** Evaluate an LDAP filter
+ *
+ * @param[in] root of the LDAP filter to evaluate.
+ * @param[in] conn LDAP connection the message being filtered was returned on.
+ * @param[in] msg to filter.
+ * @return true or false result of the node evaluation.
+ */
+bool fr_ldap_filter_eval(fr_dlist_head_t *root, fr_ldap_connection_t *conn, LDAPMessage *msg) {
+ return ldap_filter_node_eval(fr_dlist_head(root), conn, msg, 0);
+}
return len;
}
+
+/** Check that a particular attribute is included in an attribute list
+ *
+ * @param[in] attrs list to check
+ * @param[in] attr to look for
+ * @return
+ * - 1 if attr is in list
+ * - 0 if attr is missing
+ * - -1 if checks not possible
+ */
+int fr_ldap_attrs_check(char const **attrs, char const *attr)
+{
+ size_t len, i;
+
+ if (!attr) return -1;
+
+ len = talloc_array_length(attrs);
+
+ for (i = 0; i < len; i++) {
+ if (!attrs[i]) continue;
+ if (strcasecmp(attrs[i], attr) == 0) return 1;
+ if (strcasecmp(attrs[i], "*") == 0) return 1;
+ }
+
+ return 0;
+}
+
+/** Check an LDAP server entry in URL format is valid
+ *
+ * @param[in,out] handle_config LDAP handle config being built
+ * @param[in] server string to parse
+ * @param[in] cs in which the server is defined
+ * @return
+ * - 0 for valid server definition
+ * - -1 for invalid server definition
+ */
+int fr_ldap_server_url_check(fr_ldap_config_t *handle_config, char const *server, CONF_SECTION const *cs)
+{
+ LDAPURLDesc *ldap_url;
+ bool set_port_maybe = true;
+ int default_port = LDAP_PORT;
+ char *p, *url;
+ CONF_ITEM *ci = (CONF_ITEM *)cf_pair_find(cs, "server");
+
+ if (ldap_url_parse(server, &ldap_url)) {
+ cf_log_err(ci, "Parsing LDAP URL \"%s\" failed", server);
+ ldap_url_error:
+ ldap_free_urldesc(ldap_url);
+ return -1;
+ }
+
+ if (ldap_url->lud_dn && (ldap_url->lud_dn[0] != '\0')) {
+ cf_log_err(ci, "Base DN cannot be specified via server URL");
+ goto ldap_url_error;
+ }
+
+ if (ldap_url->lud_attrs && ldap_url->lud_attrs[0]) {
+ cf_log_err(ci, "Attribute list cannot be speciried via server URL");
+ goto ldap_url_error;
+ }
+
+ /*
+ * ldap_url_parse sets this to base by default.
+ */
+ if (ldap_url->lud_scope != LDAP_SCOPE_BASE) {
+ cf_log_err(ci, "Scope cannot be specified via server URL");
+ goto ldap_url_error;
+ }
+ ldap_url->lud_scope = -1; /* Otherwise LDAP adds ?base */
+
+ /*
+ * The public ldap_url_parse function sets the default
+ * port, so we have to discover whether a port was
+ * included ourselves.
+ */
+ if ((p = strchr(server, ']')) && (p[1] == ':')) { /* IPv6 */
+ set_port_maybe = false;
+ } else if ((p = strchr(server, ':')) && (strchr(p+1, ':') != NULL)) { /* IPv4 */
+ set_port_maybe = false;
+ }
+
+ /*
+ * Figure out the default port from the URL
+ */
+ if (ldap_url->lud_scheme) {
+ if (strcmp(ldap_url->lud_scheme, "ldaps") == 0) {
+ if (handle_config->start_tls == true) {
+ cf_log_err(ci, "ldap:s// scheme is not compatible with 'start_tls'");
+ goto ldap_url_error;
+ }
+ } else if (strcmp(ldap_url->lud_scheme, "ldapi") == 0) {
+ set_port_maybe = false;
+ }
+ }
+
+ if (set_port_maybe) {
+ /*
+ * URL port overrides configured port.
+ */
+ ldap_url->lud_port = handle_config->port;
+
+ /*
+ * If there's no URL port, then set it to the default
+ * this is so debugging messages show explicitly
+ * the port we're connecting to.
+ */
+ if (!ldap_url->lud_port) ldap_url->lud_port = default_port;
+ }
+
+ url = ldap_url_desc2str(ldap_url);
+ if (!url) {
+ cf_log_err(ci, "Failed recombining URL components");
+ goto ldap_url_error;
+ }
+ handle_config->server = talloc_asprintf_append(handle_config->server, "%s ", url);
+
+ ldap_free_urldesc(ldap_url);
+ ldap_memfree(url);
+ return (0);
+}
+
+/** Check an LDAP server config in server:port format is valid
+ *
+ * @param[in,out] handle_config LDAP handle config being built
+ * @param[in] server string to parse
+ * @param[in] cs in which the server is defined
+ * @return
+ * - 0 for valid server definition
+ * - -1 for invalid server definition
+ */
+int fr_ldap_server_config_check(fr_ldap_config_t *handle_config, char const *server, CONF_SECTION *cs)
+{
+ char const *p;
+ char *q;
+ int port = 0;
+ size_t len;
+
+ port = handle_config->port;
+
+ /*
+ * We don't support URLs if the library didn't provide
+ * URL parsing functions.
+ */
+ if (strchr(server, '/')) {
+ CONF_ITEM *ci;
+ bad_server_fmt:
+ ci = (CONF_ITEM *)cf_pair_find(cs, "server");
+ cf_log_err(ci, "Invalid 'server' entry, must be in format <server>[:<port>] or "
+ "an ldap URI (ldap|cldap|ldaps|ldapi)://<server>:<port>");
+ return -1;
+ }
+
+ p = strrchr(server, ':');
+ if (p) {
+ port = (int)strtol((p + 1), &q, 10);
+ if ((p == server) || ((p + 1) == q) || (*q != '\0')) goto bad_server_fmt;
+ len = p - server;
+ } else {
+ len = strlen(server);
+ }
+ if (port == 0) port = LDAP_PORT;
+
+ handle_config->server = talloc_asprintf_append(handle_config->server, "ldap://%.*s:%i ",
+ (int)len, server, port);
+ return 0;
+}
#include <freeradius-devel/util/uri.h>
#include "rlm_ldap.h"
+#include <freeradius-devel/ldap/conf.h>
#include <freeradius-devel/server/map_proc.h>
#include <freeradius-devel/server/module_rlm.h>
CONF_PARSER_TERMINATOR
};
-static CONF_PARSER sasl_mech_static[] = {
- { FR_CONF_OFFSET("mech", FR_TYPE_STRING | FR_TYPE_NOT_EMPTY, fr_ldap_sasl_t, mech) },
- { FR_CONF_OFFSET("proxy", FR_TYPE_STRING, fr_ldap_sasl_t, proxy) },
- { FR_CONF_OFFSET("realm", FR_TYPE_STRING, fr_ldap_sasl_t, realm) },
- CONF_PARSER_TERMINATOR
-};
-
-/*
- * TLS Configuration
- */
-static CONF_PARSER tls_config[] = {
- /*
- * Deprecated attributes
- */
- { FR_CONF_OFFSET("ca_file", FR_TYPE_FILE_INPUT, fr_ldap_config_t, tls_ca_file) },
-
- { FR_CONF_OFFSET("ca_path", FR_TYPE_FILE_INPUT, fr_ldap_config_t, tls_ca_path) },
-
- { FR_CONF_OFFSET("certificate_file", FR_TYPE_FILE_INPUT, fr_ldap_config_t, tls_certificate_file) },
-
- { FR_CONF_OFFSET("private_key_file", FR_TYPE_FILE_INPUT, fr_ldap_config_t, tls_private_key_file) },
-
- /*
- * LDAP Specific TLS attributes
- */
- { FR_CONF_OFFSET("start_tls", FR_TYPE_BOOL, fr_ldap_config_t, start_tls), .dflt = "no" },
-
- { FR_CONF_OFFSET("require_cert", FR_TYPE_STRING, fr_ldap_config_t, tls_require_cert_str) },
-
- { FR_CONF_OFFSET("tls_min_version", FR_TYPE_STRING, fr_ldap_config_t, tls_min_version_str) },
-
- CONF_PARSER_TERMINATOR
-};
-
-
static CONF_PARSER profile_config[] = {
{ FR_CONF_OFFSET("filter", FR_TYPE_TMPL, rlm_ldap_t, profile_filter), .dflt = "(&)", .quote = T_SINGLE_QUOTED_STRING }, //!< Correct filter for when the DN is known.
{ FR_CONF_OFFSET("attribute", FR_TYPE_STRING, rlm_ldap_t, profile_attr) },
CONF_PARSER_TERMINATOR
};
-/*
- * Various options that don't belong in the main configuration.
- *
- * Note that these overlap a bit with the connection pool code!
- */
-static CONF_PARSER option_config[] = {
- /*
- * Pool config items
- */
- { FR_CONF_OFFSET("chase_referrals", FR_TYPE_BOOL, rlm_ldap_t, handle_config.chase_referrals) },
-
- { FR_CONF_OFFSET("use_referral_credentials", FR_TYPE_BOOL, rlm_ldap_t, handle_config.use_referral_credentials), .dflt = "no" },
-
- { FR_CONF_OFFSET("referral_depth", FR_TYPE_UINT16, rlm_ldap_t, handle_config.referral_depth), .dflt = "5" },
-
- { FR_CONF_OFFSET("rebind", FR_TYPE_BOOL, rlm_ldap_t, handle_config.rebind) },
-
- { FR_CONF_OFFSET("sasl_secprops", FR_TYPE_STRING, rlm_ldap_t, handle_config.sasl_secprops) },
-
- /*
- * We use this config option to populate libldap's LDAP_OPT_NETWORK_TIMEOUT -
- * timeout on network activity - specifically libldap's initial call to "connect"
- * Must be non-zero for async connections to start correctly.
- */
- { FR_CONF_OFFSET("net_timeout", FR_TYPE_TIME_DELTA, rlm_ldap_t, handle_config.net_timeout), .dflt = "10" },
-
- { FR_CONF_OFFSET("idle", FR_TYPE_TIME_DELTA, rlm_ldap_t, handle_config.keepalive_idle), .dflt = "60" },
-
- { FR_CONF_OFFSET("probes", FR_TYPE_UINT32, rlm_ldap_t, handle_config.keepalive_probes), .dflt = "3" },
-
- { FR_CONF_OFFSET("interval", FR_TYPE_TIME_DELTA, rlm_ldap_t, handle_config.keepalive_interval), .dflt = "30" },
-
- { FR_CONF_OFFSET("dereference", FR_TYPE_STRING, rlm_ldap_t, handle_config.dereference_str) },
-
- /* allow server unlimited time for search (server-side limit) */
- { FR_CONF_OFFSET("srv_timelimit", FR_TYPE_TIME_DELTA, rlm_ldap_t, handle_config.srv_timelimit), .dflt = "20" },
-
- /*
- * Instance config items
- */
- /* timeout for search results */
- { FR_CONF_OFFSET("res_timeout", FR_TYPE_TIME_DELTA, rlm_ldap_t, handle_config.res_timeout), .dflt = "20" },
-
- { FR_CONF_OFFSET("idle_timeout", FR_TYPE_TIME_DELTA, rlm_ldap_t, handle_config.idle_timeout), .dflt = "300" },
-
- { FR_CONF_OFFSET("reconnection_delay", FR_TYPE_TIME_DELTA, rlm_ldap_t, handle_config.reconnection_delay), .dflt = "10" },
-
- CONF_PARSER_TERMINATOR
-};
-
static const CONF_PARSER module_config[] = {
/*
* Pool config items
*/
{ FR_CONF_OFFSET("server", FR_TYPE_STRING | FR_TYPE_MULTI, rlm_ldap_t, handle_config.server_str) }, /* Do not set to required */
- { FR_CONF_OFFSET("port", FR_TYPE_UINT16, rlm_ldap_t, handle_config.port) },
-
- { FR_CONF_OFFSET("identity", FR_TYPE_STRING, rlm_ldap_t, handle_config.admin_identity) },
- { FR_CONF_OFFSET("password", FR_TYPE_STRING | FR_TYPE_SECRET, rlm_ldap_t, handle_config.admin_password) },
-
- { FR_CONF_OFFSET("sasl", FR_TYPE_SUBSECTION, rlm_ldap_t, handle_config.admin_sasl), .subcs = (void const *) sasl_mech_static },
+ /*
+ * Common LDAP conf parsers
+ */
+ FR_LDAP_COMMON_CONF(rlm_ldap_t),
{ FR_CONF_OFFSET("valuepair_attribute", FR_TYPE_STRING, rlm_ldap_t, valuepair_attr) },
{ FR_CONF_POINTER("profile", FR_TYPE_SUBSECTION, NULL), .subcs = (void const *) profile_config },
- { FR_CONF_POINTER("options", FR_TYPE_SUBSECTION, NULL), .subcs = (void const *) option_config },
-
- { FR_CONF_OFFSET("tls", FR_TYPE_SUBSECTION, rlm_ldap_t, handle_config), .subcs = (void const *) tls_config },
-
{ FR_CONF_OFFSET("pool", FR_TYPE_SUBSECTION, rlm_ldap_t, trunk_conf), .subcs = (void const *) fr_trunk_config },
CONF_PARSER_TERMINATOR
* the server information in the format it needs.
*/
if (ldap_is_ldap_url(value)) {
- LDAPURLDesc *ldap_url;
- bool set_port_maybe = true;
- int default_port = LDAP_PORT;
- char *p;
- char *url;
-
- if (ldap_url_parse(value, &ldap_url)){
- cf_log_err(conf, "Parsing LDAP URL \"%s\" failed", value);
- ldap_url_error:
- ldap_free_urldesc(ldap_url);
- return -1;
- }
-
- if (ldap_url->lud_dn && (ldap_url->lud_dn[0] != '\0')) {
- cf_log_err(conf, "Base DN cannot be specified via server URL");
- goto ldap_url_error;
- }
-
- if (ldap_url->lud_attrs && ldap_url->lud_attrs[0]) {
- cf_log_err(conf, "Attribute list cannot be specified via server URL");
- goto ldap_url_error;
- }
-
- /*
- * ldap_url_parse sets this to base by default.
- */
- if (ldap_url->lud_scope != LDAP_SCOPE_BASE) {
- cf_log_err(conf, "Scope cannot be specified via server URL");
- goto ldap_url_error;
- }
- ldap_url->lud_scope = -1; /* Otherwise LDAP adds ?base */
-
- /*
- * The public ldap_url_parse function sets the default
- * port, so we have to discover whether a port was
- * included ourselves.
- */
- if ((p = strchr(value, ']')) && (p[1] == ':')) { /* IPv6 */
- set_port_maybe = false;
- } else if ((p = strchr(value, ':')) && (strchr(p + 1, ':') != NULL)) { /* IPv4 */
- set_port_maybe = false;
- }
-
- /*
- * Figure out the default port from the URL
- */
- if (ldap_url->lud_scheme) {
- if (strcmp(ldap_url->lud_scheme, "ldaps") == 0) {
- if (inst->handle_config.start_tls == true) {
- cf_log_err(conf, "ldaps:// scheme is not compatible with 'start_tls'");
- goto ldap_url_error;
- }
- default_port = LDAPS_PORT;
-
- } else if (strcmp(ldap_url->lud_scheme, "ldapi") == 0) {
- set_port_maybe = false; /* Unix socket, no port */
- }
- }
-
- if (set_port_maybe) {
- /*
- * URL port overrides configured port.
- */
- ldap_url->lud_port = inst->handle_config.port;
-
- /*
- * If there's no URL port, then set it to the default
- * this is so debugging messages show explicitly
- * the port we're connecting to.
- */
- if (!ldap_url->lud_port) ldap_url->lud_port = default_port;
- }
-
- url = ldap_url_desc2str(ldap_url);
- if (!url) {
- cf_log_err(conf, "Failed recombining URL components");
- goto ldap_url_error;
- }
- inst->handle_config.server = talloc_asprintf_append(inst->handle_config.server,
- "%s ", url);
- free(url);
-
- /*
- * @todo We could set a few other top level
- * directives using the URL, like base_dn
- * and scope.
- */
- ldap_free_urldesc(ldap_url);
- /*
- * We need to construct an LDAP URI
- */
+ if (fr_ldap_server_url_check(&inst->handle_config, value, conf) < 0) return -1;
} else
/*
- * If it's not an URL, or we don't have the functions necessary
- * to break apart the URL and recombine it, then just treat
- * server as a hostname.
+ * If it's not an URL, then just treat server as a hostname.
*/
{
- char const *p;
- char *q;
- int port = 0;
- size_t len;
-
- port = inst->handle_config.port;
-
- /*
- * We don't support URLs if the library didn't provide
- * URL parsing functions.
- */
- if (strchr(value, '/')) {
- bad_server_fmt:
- cf_log_err(conf, "Invalid 'server' entry, must be in format <server>[:<port>] or "
- "an ldap URI (ldap|cldap|ldaps|ldapi)://<server>:<port>");
- return -1;
- }
-
- p = strrchr(value, ':');
- if (p) {
- port = (int)strtol((p + 1), &q, 10);
- if ((p == value) || ((p + 1) == q) || (*q != '\0')) goto bad_server_fmt;
- len = p - value;
- } else {
- len = strlen(value);
- }
- if (port == 0) port = LDAP_PORT;
-
- inst->handle_config.server = talloc_asprintf_append(inst->handle_config.server,
- "ldap://%.*s:%i ",
- (int) len, value, port);
+ if (fr_ldap_server_config_check(&inst->handle_config, value, conf) < 0) return -1;
}
}