From: Arran Cudbard-Bell Date: Tue, 19 Mar 2013 19:33:21 +0000 (-0400) Subject: Reorganise rlm_ldap X-Git-Tag: release_3_0_0_beta1~699 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=512ef38195c4e614a13799e0364adf50691f995a;p=thirdparty%2Ffreeradius-server.git Reorganise rlm_ldap --- diff --git a/src/modules/rlm_ldap/all.mk.in b/src/modules/rlm_ldap/all.mk.in index 1459ab3dd08..ed7f72e5e00 100644 --- a/src/modules/rlm_ldap/all.mk.in +++ b/src/modules/rlm_ldap/all.mk.in @@ -4,7 +4,7 @@ ifneq "$(TARGETNAME)" "" TARGET := $(TARGETNAME).a endif -SOURCES := $(TARGETNAME).c @edir@ +SOURCES := $(TARGETNAME).c attrmap.c ldap.c @edir@ SRC_CFLAGS := @ldap_cflags@ TGT_LDLIBS := @ldap_ldflags@ diff --git a/src/modules/rlm_ldap/attrmap.c b/src/modules/rlm_ldap/attrmap.c new file mode 100644 index 00000000000..89d95d80fac --- /dev/null +++ b/src/modules/rlm_ldap/attrmap.c @@ -0,0 +1,283 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 if the + * License as published by the Free Software Foundation. + * + * 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.c + * @brief Functions for mapping between LDAP and FreeRADIUS attributes. + * + * @author Arran Cudbard-Bell + * @copyright 2013 Network RADIUS SARL + * @copyright 2013 The FreeRADIUS Server Project. + */ + +#include +#include "ldap.h" + +static VALUE_PAIR *rlm_ldap_map_getvalue(REQUEST *request, const value_pair_map_t *map, void *ctx) +{ + rlm_ldap_result_t *self = ctx; + VALUE_PAIR *head, **tail, *vp; + int i; + + request = request; + + head = NULL; + tail = &head; + + /* + * Iterate over all the retrieved values, + * don't try and be clever about changing operators + * just use whatever was set in the attribute map. + */ + for (i = 0; i < self->count; i++) { + vp = pairalloc(NULL, map->dst->da); + rad_assert(vp); + + pairparsevalue(vp, self->values[i]); + + *tail = vp; + tail = &(vp->next); + } + + return head; +} + +int rlm_ldap_map_verify(ldap_instance_t *inst, value_pair_map_t **head) +{ + value_pair_map_t *map; + + if (radius_attrmap(inst->cs, head, PAIR_LIST_REPLY, + PAIR_LIST_REQUEST, LDAP_MAX_ATTRMAP) < 0) { + return -1; + } + /* + * Attrmap only performs some basic validation checks, we need + * to do rlm_ldap specific checks here. + */ + for (map = *head; map != NULL; map = map->next) { + if (map->dst->type != VPT_TYPE_ATTR) { + cf_log_err(map->ci, "Left operand must be an attribute ref"); + + return -1; + } + + if (map->src->type == VPT_TYPE_LIST) { + cf_log_err(map->ci, "Right operand must not be a list"); + + return -1; + } + + /* + * Be smart about whether we warn the user about missing passwords. + * If there are no password attributes in the mapping, then the user's either an idiot + * and has no idea what they're doing, or they're authenticating the user using a different + * method. + */ + if (!inst->expect_password && map->dst->da && (map->dst->type == VPT_TYPE_ATTR)) { + switch (map->dst->da->attr) { + case PW_CLEARTEXT_PASSWORD: + case PW_NT_PASSWORD: + case PW_USER_PASSWORD: + case PW_PASSWORD_WITH_HEADER: + case PW_CRYPT_PASSWORD: + /* + * Because you just know someone is going to map NT-Password to the + * request list, and then complain it's not working... + */ + if (map->dst->list != PAIR_LIST_CONTROL) { + LDAP_DBGW("Mapping LDAP (%s) attribute to password \"reference\" attribute " + "(%s) in %s list. This is probably *NOT* the correct list, " + "you should prepend \"control:\" to \"reference\" attribute " + "(control:%s)", + map->src->name, map->dst->da->name, + fr_int2str(pair_lists, map->dst->list, ""), + map->dst->da->name); + } + + inst->expect_password = TRUE; + default: + break; + } + } + + switch (map->src->type) { + /* + * Only =, :=, += and -= operators are supported for + * cache entries. + */ + case VPT_TYPE_LITERAL: + case VPT_TYPE_XLAT: + case VPT_TYPE_ATTR: + switch (map->op) { + case T_OP_SET: + case T_OP_EQ: + case T_OP_SUB: + case T_OP_ADD: + break; + + default: + cf_log_err(map->ci, "Operator \"%s\" not allowed for %s values", + fr_int2str(fr_tokens, map->op, "¿unknown?"), + fr_int2str(vpt_types, map->src->type, "¿unknown?")); + return -1; + } + default: + break; + } + } + return 0; +} + +/** Free attribute map values + * + */ +void rlm_ldap_map_xlat_free(const rlm_ldap_map_xlat_t *expanded) +{ + const value_pair_map_t *map; + unsigned int total = 0; + + const char *name; + + for (map = expanded->maps; map != NULL; map = map->next) { + name = expanded->attrs[total++]; + if (!name) return; + + switch (map->src->type) { + case VPT_TYPE_XLAT: + case VPT_TYPE_ATTR: + rad_cfree(name); + break; + default: + break; + } + } +} + +/** Expand values in an attribute map where needed + * + */ +int rlm_ldap_map_xlat(REQUEST *request, const value_pair_map_t *maps, rlm_ldap_map_xlat_t *expanded) +{ + const value_pair_map_t *map; + unsigned int total = 0; + + size_t len; + char *buffer; + + VALUE_PAIR *found, **from = NULL; + REQUEST *context; + + for (map = maps; map != NULL; map = map->next) { + switch (map->src->type) { + case VPT_TYPE_XLAT: + buffer = rad_malloc(LDAP_MAX_ATTR_STR_LEN); + len = radius_xlat(buffer, LDAP_MAX_ATTR_STR_LEN, map->src->name, request, NULL, NULL); + + if (len <= 0) { + RDEBUG("Expansion of LDAP attribute \"%s\" failed", map->src->name); + + goto error; + } + + expanded->attrs[total++] = buffer; + break; + + case VPT_TYPE_ATTR: + context = request; + + if (radius_request(&context, map->src->request) == 0) { + from = radius_list(context, map->src->list); + } + if (!from) continue; + + found = pairfind(*from, map->src->da->attr, + map->src->da->vendor, TAG_ANY); + if (!found) continue; + + buffer = rad_malloc(LDAP_MAX_ATTR_STR_LEN); + strlcpy(buffer, found->vp_strvalue, LDAP_MAX_ATTR_STR_LEN); + + expanded->attrs[total++] = buffer; + break; + + case VPT_TYPE_LITERAL: + expanded->attrs[total++] = map->src->name; + break; + default: + rad_assert(0); + error: + expanded->attrs[total] = NULL; + + rlm_ldap_map_xlat_free(expanded); + + return -1; + } + + } + + expanded->attrs[total] = NULL; + expanded->maps = maps; + + return 0; +} + + +/** Convert attribute map into valuepairs + * + * Use the attribute map built earlier to convert LDAP values into valuepairs and insert them into whichever + * list they need to go into. + * + * This is *NOT* atomic, but there's no condition in which we should error out... + */ +void rlm_ldap_map_do(UNUSED const ldap_instance_t *inst, REQUEST *request, LDAP *handle, + const rlm_ldap_map_xlat_t *expanded, LDAPMessage *entry) +{ + const value_pair_map_t *map; + unsigned int total = 0; + + rlm_ldap_result_t result; + const char *name; + + for (map = expanded->maps; map != NULL; map = map->next) { + name = expanded->attrs[total++]; + + result.values = ldap_get_values(handle, entry, name); + if (!result.values) { + RDEBUG2("Attribute \"%s\" not found in LDAP object", name); + + goto next; + } + + /* + * Find out how many values there are for the + * attribute and extract all of them. + */ + result.count = ldap_count_values(result.values); + + /* + * If something bad happened, just skip, this is probably + * a case of the dst being incorrect for the current + * request context + */ + if (radius_map2request(request, map, name, rlm_ldap_map_getvalue, &result) < 0) { + goto next; + } + + next: + + ldap_value_free(result.values); + } +} diff --git a/src/modules/rlm_ldap/edir.c b/src/modules/rlm_ldap/edir.c index da55f0ec277..32688822f18 100644 --- a/src/modules/rlm_ldap/edir.c +++ b/src/modules/rlm_ldap/edir.c @@ -34,17 +34,17 @@ RCSID("$Id$") #include /* NMAS error codes */ -#define NMAS_E_BASE (-1600) +#define NMAS_E_BASE (-1600) -#define NMAS_SUCCESS 0 +#define NMAS_SUCCESS 0 -#define NMAS_E_FRAG_FAILURE (NMAS_E_BASE-31) /* -1631 0xFFFFF9A1 */ -#define NMAS_E_BUFFER_OVERFLOW (NMAS_E_BASE-33) /* -1633 0xFFFFF99F */ -#define NMAS_E_SYSTEM_RESOURCES (NMAS_E_BASE-34) /* -1634 0xFFFFF99E */ -#define NMAS_E_INSUFFICIENT_MEMORY (NMAS_E_BASE-35) /* -1635 0xFFFFF99D */ -#define NMAS_E_NOT_SUPPORTED (NMAS_E_BASE-36) /* -1636 0xFFFFF99C */ -#define NMAS_E_INVALID_PARAMETER (NMAS_E_BASE-43) /* -1643 0xFFFFF995 */ -#define NMAS_E_INVALID_VERSION (NMAS_E_BASE-52) /* -1652 0xFFFFF98C */ +#define NMAS_E_FRAG_FAILURE (NMAS_E_BASE-31) /* -1631 0xFFFFF9A1 */ +#define NMAS_E_BUFFER_OVERFLOW (NMAS_E_BASE-33) /* -1633 0xFFFFF99F */ +#define NMAS_E_SYSTEM_RESOURCES (NMAS_E_BASE-34) /* -1634 0xFFFFF99E */ +#define NMAS_E_INSUFFICIENT_MEMORY (NMAS_E_BASE-35) /* -1635 0xFFFFF99D */ +#define NMAS_E_NOT_SUPPORTED (NMAS_E_BASE-36) /* -1636 0xFFFFF99C */ +#define NMAS_E_INVALID_PARAMETER (NMAS_E_BASE-43) /* -1643 0xFFFFF995 */ +#define NMAS_E_INVALID_VERSION (NMAS_E_BASE-52) /* -1652 0xFFFFF98C */ /* OID of LDAP extenstion calls to read Universal Password */ #define NMASLDAP_GET_PASSWORD_REQUEST "2.16.840.1.113719.1.39.42.100.13" diff --git a/src/modules/rlm_ldap/ldap.c b/src/modules/rlm_ldap/ldap.c new file mode 100644 index 00000000000..d7ed802944e --- /dev/null +++ b/src/modules/rlm_ldap/ldap.c @@ -0,0 +1,1207 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 if the + * License as published by the Free Software Foundation. + * + * 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.c + * @brief LDAP module library functions. + * + * @author Arran Cudbard-Bell + * @copyright 2013 Network RADIUS SARL + * @copyright 2013 The FreeRADIUS Server Project. + */ +#include +#include +#include + +#include +#include + +#include +#include +#include "ldap.h" + + + +/** Converts "bad" strings into ones which are safe for LDAP + * + * This is a callback for xlat operations. + * + * Will escape any characters in input strings that would cause the string to be interpreted as part of a DN and or + * filter. Escape sequence is \. + * + * @param request The current request. + * @param out Pointer to output buffer. + * @param outlen Size of the output buffer. + * @param in Raw unescaped string. + * @param arg Any additional arguments (unused). + */ +size_t rlm_ldap_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, const char *in, UNUSED void *arg) +{ + static const char encode[] = ",+\"\\<>;*=()"; + static const char hextab[] = "0123456789abcdef"; + size_t left = outlen; + + if (*in && ((*in == ' ') || (*in == '#'))) { + goto encode; + } + + while (*in) { + /* + * Encode unsafe characters. + */ + if (memchr(encode, *in, sizeof(encode) - 1)) { + encode: + + /* + * Only 3 or less bytes available. + */ + if (left <= 3) break; + + *out++ = '\\'; + *out++ = hextab[(*in >> 4) & 0x0f]; + *out++ = hextab[*in & 0x0f]; + in++; + left -= 3; + + continue; + } + + if (left <= 1) break; + + /* + * Doesn't need encoding + */ + *out++ = *in++; + left--; + } + + *out = '\0'; + + return outlen - left; +} + +/** Check whether a string is a DN + * + * @param str to check. + * @return true if string is a DN, else false. + */ +int rlm_ldap_is_dn(const char *str) +{ + return strrchr(str, ',') == NULL ? FALSE : TRUE; +} + +/** Find the place at which the two DN strings diverge + * + * Returns the length of the non matching string in full. + * + * @param full DN. + * @param part Partial DN as returned by ldap_parse_result. + * @param the length of the portion of full which wasn't matched or -1 on error. + */ +static size_t rlm_ldap_common_dn(const char *full, const char *part) +{ + size_t f_len, p_len, i; + + if (!full) { + return -1; + } + + f_len = strlen(full); + + if (!part) { + return f_len; + } + + p_len = strlen(part); + if (!p_len) { + return f_len; + } + + if ((f_len < p_len) || !f_len) { + return -1; + } + + + for (i = 0; i < p_len; i++) { + if (part[p_len - i] != full[f_len - i]) { + return -1; + } + } + + return f_len - p_len; +} + +/** Parse response from LDAP server dealing with any errors + * + * Should be called after an LDAP operation. Will check result of operation and if it was successful, then attempt + * to retrieve and parse the result. + * + * Will also produce extended error output including any messages the server sent, and information about partial + * DN matches. + * + * @param[in] inst of LDAP module. + * @param[in] conn Current connection. + * @param[in] msgid returned from last operation. + * @param[in] dn Last search or bind DN. + * @param[out] result Where to write result, if NULL result will be freed. + * @param[out] error Where to write the error string, may be NULL, must not be freed. + * @param[out] extra Where to write additional error string to, may be NULL (faster) or must be freed + * (with talloc_free). + * @return One of the LDAP_PROC_* codes. + */ +static ldap_rcode_t rlm_ldap_result(const ldap_instance_t *inst, const ldap_handle_t *conn, int msgid, const char *dn, + LDAPMessage **result, const char **error, char **extra) +{ + ldap_rcode_t status = LDAP_PROC_SUCCESS; + + int lib_errno = LDAP_SUCCESS; //!< errno returned by the library. + int srv_errno = LDAP_SUCCESS; //!< errno in the result message. + + char *part_dn = NULL; //!< Partial DN match. + char *our_err = NULL; //!< Our extended error message. + char *srv_err = NULL; //!< Server's extended error message. + char *p, *a; + + int freeit = FALSE; //!< Whether the message should + //!< be freed after being processed. + int len; + + struct timeval tv; //!< Holds timeout values. + + LDAPMessage *tmp_msg; //!< Temporary message pointer storage + //!< if we weren't provided with one. + + const char *tmp_err; //!< Temporary error pointer storage + //!< if we weren't provided with one. + + if (!error) { + error = &tmp_err; + } + *error = NULL; + + if (extra) { + *extra = NULL; + } + + /* + * We always need the result, but our caller may not + */ + if (!result) { + result = &tmp_msg; + freeit = TRUE; + } + + /* + * Check if there was an error sending the request + */ + ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, + &lib_errno); + if (lib_errno != LDAP_SUCCESS) { + goto process_error; + } + + tv.tv_sec = inst->timeout; + tv.tv_usec = 0; + + /* + * Now retrieve the result and check for errors + * ldap_result returns -1 on error, and 0 on timeout + */ + lib_errno = ldap_result(conn->handle, msgid, 1, &tv, result); + if (lib_errno == 0) { + lib_errno = LDAP_TIMEOUT; + + goto process_error; + } + + if (lib_errno == -1) { + ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, + &lib_errno); + goto process_error; + } + + /* + * Parse the result and check for errors sent by the server + */ + lib_errno = ldap_parse_result(conn->handle, *result, + &srv_errno, + extra ? &part_dn : NULL, + extra ? extra : NULL, + NULL, NULL, freeit); + + if (lib_errno != LDAP_SUCCESS) { + ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, + &lib_errno); + goto process_error; + } + + process_error: + + if ((lib_errno == LDAP_SUCCESS) && (srv_errno != LDAP_SUCCESS)) { + lib_errno = srv_errno; + } else if ((lib_errno != LDAP_SUCCESS) && (srv_errno == LDAP_SUCCESS)) { + srv_errno = lib_errno; + } + + switch (lib_errno) { + case LDAP_SUCCESS: + *error = "Success"; + + break; + + case LDAP_NO_SUCH_OBJECT: + *error = "The specified object wasn't found, check basedn and admin dn"; + + status = LDAP_PROC_BAD_DN; + + if (!extra) break; + + /* + * Build our own internal diagnostic string + */ + len = rlm_ldap_common_dn(dn, part_dn); + if (len < 0) break; + + our_err = talloc_asprintf(conn, "Match stopped here: [%.*s]%s", len, part_dn, part_dn ? part_dn : ""); + + break; + + case LDAP_INSUFFICIENT_ACCESS: + *error = "Insufficient access. Check the identity and password configuration directives"; + + status = LDAP_PROC_NOT_PERMITTED; + break; + + case LDAP_UNWILLING_TO_PERFORM: + *error = "Server was unwilling to perform"; + + status = LDAP_PROC_NOT_PERMITTED; + break; + + case LDAP_TIMEOUT: + exec_trigger(NULL, inst->cs, "modules.ldap.timeout", TRUE); + + *error = "Timed out while waiting for server to respond"; + + status = LDAP_PROC_ERROR; + break; + + case LDAP_FILTER_ERROR: + *error = "Bad search filter"; + + status = LDAP_PROC_ERROR; + break; + + case LDAP_TIMELIMIT_EXCEEDED: + exec_trigger(NULL, inst->cs, "modules.ldap.timeout", TRUE); + + *error = "Time limit exceeded"; + /* FALL-THROUGH */ + + case LDAP_BUSY: + case LDAP_UNAVAILABLE: + case LDAP_SERVER_DOWN: + status = LDAP_PROC_RETRY; + + goto error_string; + + case LDAP_INVALID_CREDENTIALS: + case LDAP_CONSTRAINT_VIOLATION: + status = LDAP_PROC_REJECT; + + goto error_string; + + case LDAP_OPERATIONS_ERROR: + *error = "Please set 'chase_referrals=yes' and 'rebind=yes'. See the ldap module configuration " + "for details."; + /* FALL-THROUGH */ + + default: + status = LDAP_PROC_ERROR; + + error_string: + + if (!*error) { + *error = ldap_err2string(lib_errno); + } + + if (!extra || ((lib_errno == srv_errno) && !our_err && !srv_err)) { + break; + } + + /* + * Output the error codes from the library and server + */ + p = talloc_strdup(conn, ""); + if (!p) break; + + if (lib_errno != srv_errno) { + a = talloc_asprintf_append(p, "LDAP lib error: %s (%u), srv error: %s (%u)", + ldap_err2string(lib_errno), lib_errno, + ldap_err2string(srv_errno), srv_errno); + if (!a) { + talloc_free(p); + break; + } + + p = a; + } + + if (our_err) { + a = talloc_asprintf_append_buffer(p,". %s", our_err); + if (!a) { + talloc_free(p); + break; + } + + p = a; + } + + if (srv_err) { + a = talloc_asprintf_append_buffer(p, ". Server said: %s", srv_err); + if (!a) { + talloc_free(p); + break; + } + + p = a; + } + + *extra = p; + + break; + } + + /* + * Cleanup memory + */ + if (srv_err) { + ldap_memfree(srv_err); + } + + if (part_dn) { + ldap_memfree(part_dn); + } + + if (our_err) { + talloc_free(our_err); + } + + if ((lib_errno || srv_errno) && *result) { + ldap_msgfree(*result); + *result = NULL; + } + + return status; +} + +/** Bind to the LDAP directory as a user + * + * Performs a simple bind to the LDAP directory, and handles any errors that + * occur. + * + * @param request Current request, this may be NULL, in which case all debug logging is done with radlog. + * @param pconn to use. May change as this function auto re-connects. Caller must check that pconn is not NULL after + * calling this function. + * @param dn The DN of the user, may be NULL to bind anonymously. + * @param password May be NULL if no password is specified. + * @param retry if the server is down. + * @return one of the RLM_MODULE_* values. + */ +rlm_rcode_t rlm_ldap_bind(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn, const char *dn, + const char *password, int retry) +{ + rlm_rcode_t rcode = RLM_MODULE_OK; + ldap_rcode_t status; + + int msgid; + + const char *error = NULL; + char *extra = NULL; + + /* + * Bind as anonymous user + */ + if (!dn) dn = ""; + +retry: + msgid = ldap_bind((*pconn)->handle, dn, password, LDAP_AUTH_SIMPLE); + /* We got a valid message ID */ + if (msgid >= 0) { + if (request) { + RDEBUG2("Waiting for bind result..."); + } else { + DEBUG2("rlm_ldap (%s): Waiting for bind result...", inst->xlat_name); + } + } + + status = rlm_ldap_result(inst, *pconn, msgid, dn, NULL, &error, &extra); + switch (status) { + case LDAP_PROC_SUCCESS: + break; + case LDAP_PROC_NOT_PERMITTED: + rcode = RLM_MODULE_USERLOCK; + + LDAP_ERR_REQ("Bind was not permitted: %s", error); + LDAP_EXT_REQ(); + + break; + + case LDAP_PROC_REJECT: + rcode = RLM_MODULE_REJECT; + + LDAP_ERR_REQ("Bind credentials incorrect: %s", error); + LDAP_EXT_REQ(); + + break; + + case LDAP_PROC_RETRY: + if (retry) { + *pconn = fr_connection_reconnect(inst->pool, *pconn); + if (*pconn) { + LDAP_DBGW_REQ("Bind with %s to %s:%d failed: %s. Got new socket, retrying...", + dn, inst->server, inst->port, error); + + talloc_free(extra); /* don't leak debug info */ + + goto retry; + } + }; + + /* + * Were not allowed to retry, or there are no more + * sockets, treat this as a hard failure. + */ + goto error; + default: +error: + rcode = RLM_MODULE_FAIL; +#ifdef HAVE_LDAP_INITIALIZE + if (inst->is_url) { + LDAP_ERR_REQ("Bind with %s to %s failed: %s", dn, inst->server, error); + } else +#endif + { + LDAP_ERR_REQ("Bind with %s to %s:%d failed: %s", dn, inst->server, + inst->port, error); + } + LDAP_EXT_REQ(); + + break; + } + + if (extra) talloc_free(extra); + + return rcode; /* caller closes the connection */ +} + + +/** Search for something in the LDAP directory + * + * Binds as the administrative user and performs a search, dealing with any + * errors. + * + * @param request Current request. + * @param pconn to use. May change as this function auto re-connects. Caller must check that pconn is not NULL + * after calling this function. + * @param dn to use. + * @param scope to use. + * @param filter to use. + * @param attrs to retrieve. + * @param result Where to store the result. Must be freed with ldap_msgfree. + * may be NULL in which case result will be automatically freed after use. + * @return One of the LDAP_PROC_* values. + */ +ldap_rcode_t rlm_ldap_search(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn, + const char *dn, int scope, const char *filter, const char * const *attrs, + LDAPMessage **result) +{ + ldap_rcode_t status; + + int msgid; //!< Message id returned by + //!< ldap_search_ext. + + int count = 0; //!< Number of results we got. + + struct timeval tv; //!< Holds timeout values. + + const char *error = NULL; + char *extra = NULL; + + /* + * OpenLDAP library doesn't declare attrs array as const, but + * it really should be *sigh*. + */ + char **search_attrs; + memcpy(&search_attrs, &attrs, sizeof(attrs)); + + /* + * Do all searches as the admin user. + */ + if ((*pconn)->rebound) { + if (rlm_ldap_bind(inst, request, pconn, inst->login, inst->password, TRUE) != RLM_MODULE_OK) { + return LDAP_PROC_ERROR; + } + + rad_assert(*pconn); + + (*pconn)->rebound = FALSE; + } + + RDEBUG2("Performing search in '%s' with filter '%s'", dn, filter); + + /* + * If LDAP search produced an error it should also be logged + * to the ld. result should pick it up without us + * having to pass it explicitly. + */ + tv.tv_sec = inst->timeout; + tv.tv_usec = 0; +retry: + (void) ldap_search_ext((*pconn)->handle, dn, scope, filter, search_attrs, 0, NULL, NULL, &tv, 0, &msgid); + + RDEBUG2("Waiting for search result..."); + status = rlm_ldap_result(inst, *pconn, msgid, dn, result, &error, &extra); + switch (status) { + case LDAP_PROC_SUCCESS: + break; + case LDAP_PROC_RETRY: + *pconn = fr_connection_reconnect(inst->pool, *pconn); + if (*pconn) { + RDEBUGW("Search failed: %s. Got new socket, retrying...", error); + + talloc_free(extra); /* don't leak debug info */ + + goto retry; + } + + status = LDAP_PROC_ERROR; + + /* FALL-THROUGH */ + default: + RDEBUGE("Failed performing search: %s", error); + RDEBUGE("%s", extra); + + goto finish; + } + + if (result) { + count = ldap_count_entries((*pconn)->handle, *result); + if (count == 0) { + ldap_msgfree(*result); + *result = NULL; + + RDEBUG("Search returned no results"); + + status = LDAP_PROC_NO_RESULT; + } + } + + finish: + if (extra) talloc_free(extra); + + return status; +} + +/** Modify something in the LDAP directory + * + * Binds as the administrative user and attempts to modify an LDAP object. + * + * @param request Current request. + * @param pconn to use. May change as this function auto re-connects. Caller must check that pconn is not NULL after + * calling this function. + * @param dn to modify. + * @param mods to make. + * @return One of the LDAP_PROC_* values. + */ +ldap_rcode_t rlm_ldap_modify(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn, + const char *dn, LDAPMod *mods[]) +{ + ldap_rcode_t status; + + int msgid; //!< Message id returned by + //!< ldap_search_ext. + + const char *error = NULL; + char *extra = NULL; + + /* + * Perform all modifications as the admin user. + */ + if ((*pconn)->rebound) { + if (rlm_ldap_bind(inst, request, pconn, inst->login, inst->password, TRUE) != RLM_MODULE_OK) { + return LDAP_PROC_ERROR; + } + + rad_assert(*pconn); + + (*pconn)->rebound = FALSE; + } + + RDEBUG2("Modifying object with DN \"%s\"", dn); + retry: + (void) ldap_modify_ext((*pconn)->handle, dn, mods, NULL, NULL, &msgid); + + RDEBUG2("Waiting for modify result..."); + status = rlm_ldap_result(inst, *pconn, msgid, dn, NULL, &error, &extra); + switch (status) { + case LDAP_PROC_SUCCESS: + break; + case LDAP_PROC_RETRY: + *pconn = fr_connection_reconnect(inst->pool, *pconn); + if (*pconn) { + RDEBUGW("Modify failed: %s. Got new socket, retrying...", error); + + talloc_free(extra); /* don't leak debug info */ + + goto retry; + } + + status = LDAP_PROC_ERROR; + + /* FALL-THROUGH */ + default: + RDEBUGE("Failed modifying object: %s", error); + RDEBUGE("%s", extra); + + goto finish; + } + + finish: + if (extra) talloc_free(extra); + + return status; +} + +/** Retrieve the DN of a user object + * + * Retrieves the DN of a user and adds it to the control list as LDAP-UserDN. Will also retrieve any attributes + * passed and return the result in *result. + * + * This potentially allows for all authorization and authentication checks to be performed in one ldap search + * operation, which is a big bonus given the number of crappy, slow *cough*AD*cough* LDAP directory servers out there. + * + * @param[in] request Current request. + * @param[in,out] pconn to use. May change as this function auto re-connects. Caller must check that pconn is not NULL + * after calling this function. + * @param[in] attrs Additional attributes to retrieve, may be NULL. + * @param[out] result Where to write the result, may be NULL in which case result is discarded. + * @param[out] rcode The status of the operation, one of the RLM_MODULE_* codes. + * @return The user's DN or NULL on error. + */ +const char *rlm_ldap_find_user(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn, + const char *attrs[], LDAPMessage **result, rlm_rcode_t *rcode) +{ + static const char *tmp_attrs[] = { NULL }; + + ldap_rcode_t status; + VALUE_PAIR *vp = NULL; + LDAPMessage *tmp_msg = NULL, *entry = NULL; + int ldap_errno; + char *dn; + char filter[LDAP_MAX_FILTER_STR_LEN]; + char basedn[LDAP_MAX_FILTER_STR_LEN]; + + int freeit = FALSE; //!< Whether the message should + //!< be freed after being processed. + + *rcode = RLM_MODULE_FAIL; + + if (!result) { + result = &tmp_msg; + freeit = TRUE; + } + *result = NULL; + + if (!attrs) { + memset(&attrs, 0, sizeof(tmp_attrs)); + } + + /* + * If the caller isn't looking for the result we can just return the current userdn value. + */ + if (!result) { + vp = pairfind(request->config_items, PW_LDAP_USERDN, 0, TAG_ANY); + if (vp) { + *rcode = RLM_MODULE_OK; + return vp->vp_strvalue; + } + } + + /* + * Perform all searches as the admin user. + */ + if ((*pconn)->rebound) { + if (rlm_ldap_bind(inst, request, pconn, inst->login, inst->password, TRUE) != RLM_MODULE_OK) { + *rcode = RLM_MODULE_FAIL; + return NULL; + } + + rad_assert(*pconn); + + (*pconn)->rebound = FALSE; + } + + + if (!radius_xlat(filter, sizeof(filter), inst->filter, request, rlm_ldap_escape_func, NULL)) { + RDEBUGE("Unable to create filter"); + + *rcode = RLM_MODULE_INVALID; + return NULL; + } + + if (!radius_xlat(basedn, sizeof(basedn), inst->basedn, request, rlm_ldap_escape_func, NULL)) { + RDEBUGE("Unable to create basedn"); + + *rcode = RLM_MODULE_INVALID; + return NULL; + } + + status = rlm_ldap_search(inst, request, pconn, basedn, LDAP_SCOPE_SUBTREE, filter, attrs, result); + switch (status) { + case LDAP_PROC_SUCCESS: + break; + case LDAP_PROC_NO_RESULT: + *rcode = RLM_MODULE_NOTFOUND; + return NULL; + default: + return NULL; + } + + rad_assert(*pconn); + + entry = ldap_first_entry((*pconn)->handle, *result); + if (!entry) { + ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); + RDEBUGE("Failed retrieving entry: %s", + ldap_err2string(ldap_errno)); + + goto finish; + } + + dn = ldap_get_dn((*pconn)->handle, entry); + if (!dn) { + ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); + + RDEBUGE("Retrieving object DN from entry failed: %s", + ldap_err2string(ldap_errno)); + + goto finish; + } + + vp = pairmake("LDAP-UserDn", dn, T_OP_EQ); + if (vp) { + pairadd(&request->config_items, vp); + *rcode = RLM_MODULE_OK; + } + + finish: + ldap_memfree(dn); + + if ((freeit || (*rcode != RLM_MODULE_OK)) && *result) { + ldap_msgfree(*result); + *result = NULL; + } + + return vp ? vp->vp_strvalue : NULL; +} + +rlm_rcode_t rlm_ldap_apply_profile(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn, + const char *profile, const rlm_ldap_map_xlat_t *expanded) +{ + rlm_rcode_t rcode = RLM_MODULE_OK; + ldap_rcode_t status; + LDAPMessage *result = NULL, *entry = NULL; + int ldap_errno; + LDAP *handle = (*pconn)->handle; + char filter[LDAP_MAX_FILTER_STR_LEN]; + + if (!profile || !*profile) { + return RLM_MODULE_NOOP; + } + strlcpy(filter, inst->base_filter, sizeof(filter)); + + status = rlm_ldap_search(inst, request, pconn, profile, LDAP_SCOPE_BASE, filter, expanded->attrs, &result); + switch (status) { + case LDAP_PROC_SUCCESS: + break; + case LDAP_PROC_NO_RESULT: + RDEBUG("Profile \"%s\" not found", profile); + return RLM_MODULE_NOTFOUND; + default: + return RLM_MODULE_FAIL; + } + + rad_assert(*pconn); + rad_assert(result); + + entry = ldap_first_entry(handle, result); + if (!entry) { + ldap_get_option(handle, LDAP_OPT_RESULT_CODE, &ldap_errno); + RDEBUGE("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); + + rcode = RLM_MODULE_NOTFOUND; + + goto free_result; + } + + rlm_ldap_map_do(inst, request, handle, expanded, entry); + +free_result: + ldap_msgfree(result); + + return rcode; +} + +/** Check for presence of access attribute in result + * + * @param inst rlm_ldap configuration. + * @param request Current request. + * @param conn used to retrieve entry. + * @param entry retrieved by rlm_ldap_find_user or rlm_ldap_search. + * @return RLM_MODULE_USERLOCK if the user was denied access, else RLM_MODULE_OK. + */ +rlm_rcode_t rlm_ldap_check_access(const ldap_instance_t *inst, REQUEST *request, const ldap_handle_t *conn, + LDAPMessage *entry) +{ + rlm_rcode_t rcode = RLM_MODULE_OK; + char **vals = NULL; + + vals = ldap_get_values(conn->handle, entry, inst->userobj_access_attr); + if (vals) { + if (inst->access_positive && (strncmp(vals[0], "FALSE", 5) == 0)) { + RDEBUG("\"%s\" attribute exists but is set to 'false' - user locked out"); + rcode = RLM_MODULE_USERLOCK; + } else { + RDEBUG("\"%s\" attribute exists - user locked out", inst->userobj_access_attr); + rcode = RLM_MODULE_USERLOCK; + } + + ldap_value_free(vals); + } else if (inst->access_positive) { + RDEBUG("No \"%s\" attribute - user locked out", inst->userobj_access_attr); + rcode = RLM_MODULE_USERLOCK; + } + + return rcode; +} + +/** Verify we got a password from the search + * + * Checks to see if after the LDAP to RADIUS mapping has been completed that a reference password. + * + * @param inst rlm_ldap configuration. + * @param request Current request. + */ +void rlm_ldap_check_reply(const ldap_instance_t *inst, REQUEST *request) +{ + /* + * More warning messages for people who can't be bothered to read the documentation. + * + * Expect_password is set when we process the mapping, and is only true if there was a mapping between + * an LDAP attribute and a password reference attribute in the control list. + */ + if (inst->expect_password && (debug_flag > 1)) { + if (!pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY) && + !pairfind(request->config_items, PW_NT_PASSWORD, 0, TAG_ANY) && + !pairfind(request->config_items, PW_USER_PASSWORD, 0, TAG_ANY) && + !pairfind(request->config_items, PW_PASSWORD_WITH_HEADER, 0, TAG_ANY) && + !pairfind(request->config_items, PW_CRYPT_PASSWORD, 0, TAG_ANY)) { + RDEBUGW("No \"reference\" password added. Ensure the admin user has permission to " + "read the password attribute"); + RDEBUGW("PAP authentication will *NOT* work with Active Directory (if that is what you " + "were trying to configure)"); + } + } +} + +#if LDAP_SET_REBIND_PROC_ARGS == 3 +/** Callback for OpenLDAP to rebind and chase referrals + * + * Called by OpenLDAP when it receives a referral and has to rebind. + * + * @param handle to rebind. + * @param url to bind to. + * @param request that triggered the rebind. + * @param msgid that triggered the rebind. + * @param ctx rlm_ldap configuration. + */ +static int rlm_ldap_rebind(LDAP *handle, LDAP_CONST char *url, UNUSED ber_tag_t request, UNUSED ber_int_t msgid, + void *ctx) +{ + rlm_rcode_t rcode; + ldap_handle_t *conn = ctx; + + int ldap_errno; + + conn->referred = TRUE; + conn->rebound = TRUE; /* not really, but oh well... */ + rad_assert(handle == conn->handle); + + DEBUG("rlm_ldap (%s): Rebinding to URL %s", conn->inst->xlat_name, url); + + rcode = rlm_ldap_bind(conn->inst, NULL, &conn, conn->inst->login, conn->inst->password, FALSE); + + if (rcode == RLM_MODULE_OK) { + return LDAP_SUCCESS; + } + + ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); + + return ldap_errno; +} +#endif + +/** Create and return a new connection + * + * Create a new ldap connection and allocate memory for a new rlm_handle_t + * + * @param ctx rlm_ldap instance. + * @return A new connection handle or NULL on error. + */ +void *rlm_ldap_conn_create(void *ctx) +{ + rlm_rcode_t rcode; + + int ldap_errno, ldap_version; + struct timeval tv; + + ldap_instance_t *inst = ctx; + LDAP *handle = NULL; + ldap_handle_t *conn = NULL; + +#ifdef HAVE_LDAP_INITIALIZE + if (inst->is_url) { + DEBUG("rlm_ldap (%s): Connecting to %s", inst->xlat_name, inst->server); + + ldap_errno = ldap_initialize(&handle, inst->server); + if (ldap_errno != LDAP_SUCCESS) { + LDAP_ERR("ldap_initialize failed: %s", ldap_err2string(ldap_errno)); + goto error; + } + } else +#endif + { + DEBUG("rlm_ldap (%s): Connecting to %s:%d", inst->xlat_name, inst->server, inst->port); + + handle = ldap_init(inst->server, inst->port); + if (!handle) { + LDAP_ERR("ldap_init() failed"); + goto error; + } + } + + /* + * We now have a connection structure, but no actual TCP connection. + * + * Set a bunch of LDAP options, using common code. + */ +#define do_ldap_option(_option, _name, _value) \ + if (ldap_set_option(handle, _option, _value) != LDAP_OPT_SUCCESS) { \ + ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); \ + LDAP_ERR("Could not set %s: %s", _name, ldap_err2string(ldap_errno)); \ + } + + if (inst->ldap_debug) { + do_ldap_option(LDAP_OPT_DEBUG_LEVEL, "ldap_debug", &(inst->ldap_debug)); + } + + /* + * Leave "chase_referrals" unset to use the OpenLDAP default. + */ + if (inst->chase_referrals != 2) { + if (inst->chase_referrals) { + do_ldap_option(LDAP_OPT_REFERRALS, "chase_referrals", LDAP_OPT_ON); + + if (inst->rebind == 1) { +#if LDAP_SET_REBIND_PROC_ARGS == 3 + ldap_set_rebind_proc(handle, rlm_ldap_rebind, inst); +#else + DEBUGW("The flag 'rebind = yes' is not supported by the system LDAP library. " + "Ignoring."); +#endif + } + } else { + do_ldap_option(LDAP_OPT_REFERRALS, "chase_referrals", LDAP_OPT_OFF); + } + } + + tv.tv_sec = inst->net_timeout; + tv.tv_usec = 0; + do_ldap_option(LDAP_OPT_NETWORK_TIMEOUT, "net_timeout", &tv); + + do_ldap_option(LDAP_OPT_TIMELIMIT, "timelimit", &(inst->timelimit)); + + ldap_version = LDAP_VERSION3; + do_ldap_option(LDAP_OPT_PROTOCOL_VERSION, "ldap_version", &ldap_version); + +#ifdef LDAP_OPT_X_KEEPALIVE_IDLE + do_ldap_option(LDAP_OPT_X_KEEPALIVE_IDLE, "keepalive idle", &(inst->keepalive_idle)); +#endif + +#ifdef LDAP_OPT_X_KEEPALIVE_PROBES + do_ldap_option(LDAP_OPT_X_KEEPALIVE_PROBES, "keepalive probes", &(inst->keepalive_probes)); +#endif + +#ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL + do_ldap_option(LDAP_OPT_X_KEEPALIVE_INTERVAL, "keepalive interval", &(inst->keepalive_interval)); +#endif + +#ifdef HAVE_LDAP_START_TLS + /* + * Set all of the TLS options + */ + if (inst->tls_mode) { + do_ldap_option(LDAP_OPT_X_TLS, "tls_mode", &(inst->tls_mode)); + } + +# define maybe_ldap_option(_option, _name, _value) \ + if (_value) do_ldap_option(_option, _name, _value) + + maybe_ldap_option(LDAP_OPT_X_TLS_CACERTFILE, "cacertfile", inst->tls_cacertfile); + maybe_ldap_option(LDAP_OPT_X_TLS_CACERTDIR, "cacertdir", inst->tls_cacertdir); + +# ifdef HAVE_LDAP_INT_TLS_CONFIG + if (ldap_int_tls_config(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, inst->tls_require_cert) != LDAP_OPT_SUCCESS) { + ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); + + LDAP_ERR("Could not set LDAP_OPT_X_TLS_REQUIRE_CERT option to %s: %s", inst->tls_require_cert, + ldap_err2string(ldap_errno)); + } +# endif + + /* + * Set certificate options + */ + maybe_ldap_option(LDAP_OPT_X_TLS_CERTFILE, "certfile", inst->tls_certfile); + maybe_ldap_option(LDAP_OPT_X_TLS_KEYFILE, "keyfile", inst->tls_keyfile); + maybe_ldap_option(LDAP_OPT_X_TLS_RANDOM_FILE, "randfile", inst->tls_randfile); + + /* + * And finally start the TLS code. + */ + if (inst->start_tls) { + if (inst->port == 636) { + DEBUGW("Told to Start TLS on LDAPS port this will probably fail, please correct the " + "configuration"); + } + + if (ldap_start_tls_s(handle, NULL, NULL) != LDAP_SUCCESS) { + ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); + + LDAP_ERR("Could not start TLS: %s", ldap_err2string(ldap_errno)); + goto error; + } + } +#endif /* HAVE_LDAP_START_TLS */ + + /* + * Allocate memory for the handle. + */ + conn = talloc_zero(ctx, ldap_handle_t); + conn->inst = inst; + conn->handle = handle; + conn->rebound = FALSE; + conn->referred = FALSE; + + rcode = rlm_ldap_bind(inst, NULL, &conn, inst->login, inst->password, FALSE); + if (rcode != RLM_MODULE_OK) { + goto error; + } + + return conn; + + error: + if (handle) ldap_unbind_s(handle); + if (conn) talloc_free(conn); + + return NULL; +} + + +/** Close and delete a connection + * + * Unbinds the LDAP connection, informing the server and freeing any memory, then releases the memory used by the + * connection handle. + * + * @param ctx unused. + * @param connection to destroy. + * @return always indicates success. + */ +int rlm_ldap_conn_delete(UNUSED void *ctx, void *connection) +{ + ldap_handle_t *conn = connection; + + ldap_unbind_s(conn->handle); + talloc_free(conn); + + return 0; +} + + +/** Gets an LDAP socket from the connection pool + * + * Retrieve a socket from the connection pool, or NULL on error (of if no sockets are available). + * + * @param inst rlm_ldap configuration. + * @param request Current request. + */ +ldap_handle_t *rlm_ldap_get_socket(const ldap_instance_t *inst, REQUEST *request) +{ + ldap_handle_t *conn; + + conn = fr_connection_get(inst->pool); + if (!conn) { + RDEBUGE("All ldap connections are in use"); + + return NULL; + } + + return conn; +} + +/** Frees an LDAP socket back to the connection pool + * + * If the socket was rebound chasing a referral onto another server then we destroy it. + * If the socket was rebound to another user on the same server, we let the next caller rebind it. + * + * @param inst rlm_ldap configuration. + * @param conn to release. + */ +void rlm_ldap_release_socket(const ldap_instance_t *inst, ldap_handle_t *conn) +{ + /* + * Could have already been free'd due to a previous error. + */ + if (!conn) return; + + /* + * We chased a referral to another server. + * + * This connection is no longer part of the pool which is connected to and bound to the configured server. + * Close it. + * + * Note that we do NOT close it if it was bound to another user. Instead, we let the next caller do the + * rebind. + */ + if (conn->referred) { + fr_connection_del(inst->pool, conn); + return; + } + + fr_connection_release(inst->pool, conn); + return; +} diff --git a/src/modules/rlm_ldap/ldap.h b/src/modules/rlm_ldap/ldap.h new file mode 100644 index 00000000000..28a30e280a6 --- /dev/null +++ b/src/modules/rlm_ldap/ldap.h @@ -0,0 +1,247 @@ +/** + * $Id$ + * @file ldap. + * @brief LDAP authorization and authentication module headers. + * + * @author Arran Cudbard-Bell + * @copyright 2013 Network RADIUS SARL + * @copyright 2013 The FreeRADIUS Server Project. + */ +#ifndef _RLM_LDAP_H +#define _RLM_LDAP_H + +#include +#include +#include + +#define LDAP_MAX_ATTRMAP 128 +#define LDAP_MAX_ATTR_STR_LEN 256 +#define LDAP_MAX_FILTER_STR_LEN 1024 + +/* + * The default setting for TLS Certificate Verification + */ +#define TLS_DEFAULT_VERIFY "allow" + +typedef struct ldap_acct_section { + CONF_SECTION *cs; + + const char *reference; +} ldap_acct_section_t; + +typedef struct ldap_instance { + CONF_SECTION *cs; + fr_connection_pool_t *pool; + + char *server; + int port; + + char *login; + char *password; + + char *filter; + char *basedn; + + int chase_referrals; + int rebind; + + int ldap_debug; //!< Debug flag for the SDK. + + const char *xlat_name; //!< Instance name. + + int expect_password; + + /* + * RADIUS attribute to LDAP attribute maps + */ + value_pair_map_t *user_map; //!< Attribute map applied + //!< to users and profiles. + + /* + * User object attributes and filters + */ + const char *userobj_filter; //!< Filter to retrieve only + //!< user objects. + const char *userobj_membership_attr; //!< Attribute that + //!< describes groups + //!< the user is a + //!< member of. + char *userobj_access_attr; //!< Attribute to check to see + //!< if the user should be + //!< locked out. + int access_positive; //!< If true the presence of + //!< the attribute will allow + //!< access, else it will + //!< deny access. + + /* + * Group object attributes and filters + */ + const char *groupobj_name_attr; //!< The name of the group. + const char *groupobj_membership_filter; //!< Filter to only + //!< retrieve groups + //!< which contain + //!< the user as a + //!< member. + + /* + * Profiles + */ + const char *base_filter; //!< Base filter combined with + //!< all other filters. + const char *default_profile; + const char *profile_attr; + + + /* + * Accounting + */ + ldap_acct_section_t *postauth; + ldap_acct_section_t *accounting; + + /* + * TLS items. We should really normalize these with the + * TLS code in 3.0. + */ + int tls_mode; + int start_tls; + char *tls_cacertfile; + char *tls_cacertdir; + char *tls_certfile; + char *tls_keyfile; + char *tls_randfile; + char *tls_require_cert; + + /* + * Options + */ + int timelimit; + int net_timeout; + int timeout; + int is_url; + +#ifdef WITH_EDIR + /* + * eDir support + */ + int edir; + int edir_autz; +#endif + /* + * For keep-alives. + */ +#ifdef LDAP_OPT_X_KEEPALIVE_IDLE + int keepalive_idle; +#endif +#ifdef LDAP_OPT_X_KEEPALIVE_PROBES + int keepalive_probes; +#endif +#ifdef LDAP_OPT_ERROR_NUMBER + int keepalive_interval; +#endif + +} ldap_instance_t; + +typedef struct ldap_handle { + LDAP *handle; //!< LDAP LD handle. + int rebound; //!< Whether the connection has been rebound to something other than the admin + //!< user. + int referred; //!< Whether the connection is now established a server other than the + //!< configured one. + ldap_instance_t *inst; //!< rlm_ldap configuration. +} ldap_handle_t; + +typedef struct rlm_ldap_map_xlat { + const value_pair_map_t *maps; + const char *attrs[LDAP_MAX_ATTRMAP]; +} rlm_ldap_map_xlat_t; + +typedef struct rlm_ldap_result { + char **values; + int count; +} rlm_ldap_result_t; + +typedef enum { + LDAP_PROC_SUCCESS = 0, //!< Operation was successfull. + LDAP_PROC_ERROR = -1, //!< Unrecoverable library/server error. + LDAP_PROC_RETRY = -2, //!< Transitory error, caller should + //!< retry the operation with a new + //!< connection. + LDAP_PROC_NOT_PERMITTED = -3, //!< Operation was not permitted, + //!< either current user was locked out + //!< in the case of binds, or has + //!< insufficient access. + LDAP_PROC_REJECT = -4, //!< Bind failed, user was rejected. + LDAP_PROC_BAD_DN = -5, //!< Specified an invalid object in a + //!< bind or search DN. + LDAP_PROC_NO_RESULT = -6 //!< Got no results. +} ldap_rcode_t; + +/* + * Some functions may be called with a NULL request structure, this + * simplifies switching certain messages from the request log to + * the main log. + */ +#define LDAP_INFO(fmt, ...) radlog(L_INFO, "rlm_ldap (%s): " fmt, inst->xlat_name, ##__VA_ARGS__) + +#define LDAP_DBGW(fmt, ...) radlog(L_DBG_WARN, "rlm_ldap (%s): " fmt, inst->xlat_name, ##__VA_ARGS__) +#define LDAP_DBGW_REQ(fmt, ...) do { if (request) {RDEBUGW(fmt, ##__VA_ARGS__);} else {LDAP_DBGW(fmt, ##__VA_ARGS__);}} while (0) + +#define LDAP_ERR(fmt, ...) radlog(L_ERR, "rlm_ldap (%s): " fmt, inst->xlat_name, ##__VA_ARGS__) +#define LDAP_ERR_REQ(fmt, ...) do { if (request) {RDEBUGE(fmt, ##__VA_ARGS__);} else {LDAP_ERR(fmt, ##__VA_ARGS__);}} while (0) + +#define LDAP_EXT() if (extra) LDAP_ERR(extra) +#define LDAP_EXT_REQ() do { if (extra) { if (request) RDEBUGE("%s", extra); else LDAP_ERR("%s", extra); }} while (0) + +/* + * ldap.c - Wrappers arounds OpenLDAP functions. + */ +size_t rlm_ldap_escape_func(UNUSED REQUEST *request, char *out, size_t outlen, const char *in, UNUSED void *arg); + +int rlm_ldap_is_dn(const char *str); + +rlm_rcode_t rlm_ldap_bind(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn, const char *dn, + const char *password, int retry); + +ldap_rcode_t rlm_ldap_search(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn, + const char *dn, int scope, const char *filter, const char * const *attrs, + LDAPMessage **result); + +ldap_rcode_t rlm_ldap_modify(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn, + const char *dn, LDAPMod *mods[]); + +rlm_rcode_t rlm_ldap_apply_profile(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn, + const char *profile, const rlm_ldap_map_xlat_t *expanded); + +const char *rlm_ldap_find_user(const ldap_instance_t *inst, REQUEST *request, ldap_handle_t **pconn, + const char *attrs[], LDAPMessage **result, rlm_rcode_t *rcode); + +rlm_rcode_t rlm_ldap_check_access(const ldap_instance_t *inst, REQUEST *request, const ldap_handle_t *conn, + LDAPMessage *entry); + +void rlm_ldap_check_reply(const ldap_instance_t *inst, REQUEST *request); + +/* + * ldap.c - Callbacks for the connection pool API. + */ +void *rlm_ldap_conn_create(void *ctx); + +int rlm_ldap_conn_delete(UNUSED void *ctx, void *connection); + +ldap_handle_t *rlm_ldap_get_socket(const ldap_instance_t *inst, REQUEST *request); + +void rlm_ldap_release_socket(const ldap_instance_t *inst, ldap_handle_t *conn); + +/* + * attrmap.c - Attribute mapping code. + */ +int rlm_ldap_map_verify(ldap_instance_t *inst, value_pair_map_t **head); + +void rlm_ldap_map_xlat_free(const rlm_ldap_map_xlat_t *expanded); + +int rlm_ldap_map_xlat(REQUEST *request, const value_pair_map_t *maps, rlm_ldap_map_xlat_t *expanded); + +void rlm_ldap_map_do(const ldap_instance_t *inst, REQUEST *request, LDAP *handle, + const rlm_ldap_map_xlat_t *expanded, LDAPMessage *entry); + +#endif diff --git a/src/modules/rlm_ldap/rlm_ldap.c b/src/modules/rlm_ldap/rlm_ldap.c index 81f1df4ce5e..d466e97d402 100644 --- a/src/modules/rlm_ldap/rlm_ldap.c +++ b/src/modules/rlm_ldap/rlm_ldap.c @@ -18,201 +18,69 @@ * @file rlm_ldap.c * @brief LDAP authorization and authentication module. * - * @copyright 1999-2013 The FreeRADIUS Server Project. - * @copyright 2012 Alan DeKok + * @author Arran Cudbard-Bell + * @author Alan DeKok + * + * @copyright 2013 Network RADIUS SARL * @copyright 2012-2013 Arran Cudbard-Bell + * @copyright 2012 Alan DeKok + * @copyright 1999-2013 The FreeRADIUS Server Project. */ #include RCSID("$Id$") -#include -#include #include #include #include -#include -#include - -#define MAX_ATTRMAP 128 -#define MAX_ATTR_STR_LEN 256 -#define MAX_FILTER_STR_LEN 1024 - -#ifdef WITH_EDIR -extern int nmasldap_get_password(LDAP *ld,char *objectDN, char *pwd, size_t *pwdSize); - -#endif - -typedef struct ldap_acct_section { - CONF_SECTION *cs; - - const char *reference; -} ldap_acct_section_t; - - -typedef struct { - CONF_SECTION *cs; - fr_connection_pool_t *pool; - - char *server; - int port; - - char *login; - char *password; - - char *filter; - char *basedn; - - int chase_referrals; - int rebind; - - int ldap_debug; //!< Debug flag for the SDK. - - const char *xlat_name; //!< Instance name. - - int expect_password; - - /* - * RADIUS attribute to LDAP attribute maps - */ - value_pair_map_t *user_map; //!< Attribute map applied to users and - //!< profiles. - - /* - * Access related configuration - */ - char *access_attr; - int positive_access_attr; - - /* - * Profiles - */ - char *base_filter; - char *default_profile; - char *profile_attr; - - /* - * Group checking. - */ - char *groupname_attr; - char *groupmemb_filter; - char *groupmemb_attr; - - /* - * Accounting - */ - ldap_acct_section_t *postauth; - ldap_acct_section_t *accounting; - - /* - * TLS items. We should really normalize these with the - * TLS code in 3.0. - */ - int tls_mode; - int start_tls; - char *tls_cacertfile; - char *tls_cacertdir; - char *tls_certfile; - char *tls_keyfile; - char *tls_randfile; - char *tls_require_cert; - - /* - * Options - */ - int timelimit; - int net_timeout; - int timeout; - int is_url; +#include "ldap.h" #ifdef WITH_EDIR - /* - * eDir support - */ - int edir; - int edir_autz; -#endif - /* - * For keep-alives. - */ -#ifdef LDAP_OPT_X_KEEPALIVE_IDLE - int keepalive_idle; -#endif -#ifdef LDAP_OPT_X_KEEPALIVE_PROBES - int keepalive_probes; +extern int nmasldap_get_password(LDAP *ld, char *objectDN, char *pwd, + size_t *pwdSize); #endif -#ifdef LDAP_OPT_ERROR_NUMBER - int keepalive_interval; -#endif - -} ldap_instance; - -/* The default setting for TLS Certificate Verification */ -#define TLS_DEFAULT_VERIFY "allow" /* * TLS Configuration */ static CONF_PARSER tls_config[] = { - {"start_tls", PW_TYPE_BOOLEAN, - offsetof(ldap_instance,start_tls), NULL, "no"}, - {"cacertfile", PW_TYPE_FILENAME, - offsetof(ldap_instance,tls_cacertfile), NULL, NULL}, - {"cacertdir", PW_TYPE_FILENAME, - offsetof(ldap_instance,tls_cacertdir), NULL, NULL}, - {"certfile", PW_TYPE_FILENAME, - offsetof(ldap_instance,tls_certfile), NULL, NULL}, - {"keyfile", PW_TYPE_FILENAME, - offsetof(ldap_instance,tls_keyfile), NULL, NULL}, - {"randfile", PW_TYPE_STRING_PTR, /* OK if it changes on HUP */ - offsetof(ldap_instance,tls_randfile), NULL, NULL}, - {"require_cert", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,tls_require_cert), NULL, TLS_DEFAULT_VERIFY}, + {"start_tls", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t, start_tls), NULL, "no"}, + {"cacertfile", PW_TYPE_FILENAME, offsetof(ldap_instance_t, tls_cacertfile), NULL, NULL}, + {"cacertdir", PW_TYPE_FILENAME, offsetof(ldap_instance_t, tls_cacertdir), NULL, NULL}, + {"certfile", PW_TYPE_FILENAME, offsetof(ldap_instance_t, tls_certfile), NULL, NULL}, + {"keyfile", PW_TYPE_FILENAME, offsetof(ldap_instance_t, tls_keyfile), NULL, NULL}, // OK if it changes on HUP + {"randfile", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, tls_randfile), NULL, NULL}, + {"require_cert",PW_TYPE_STRING_PTR, offsetof(ldap_instance_t, tls_require_cert), NULL, TLS_DEFAULT_VERIFY}, { NULL, -1, 0, NULL, NULL } }; - +/* + * Access limitations + */ static CONF_PARSER attr_config[] = { - /* - * Access limitations - */ /* LDAP attribute name that controls remote access */ - {"access_attr", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,access_attr), NULL, NULL}, - {"positive_access_attr", PW_TYPE_BOOLEAN, - offsetof(ldap_instance,positive_access_attr), NULL, "yes"}, - - {"base_filter", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,base_filter), NULL, - "(objectclass=radiusprofile)"}, - {"default_profile", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,default_profile), NULL, NULL}, - {"profile_attribute", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,profile_attr), NULL, NULL}, + {"userobj_access_attr", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,userobj_access_attr), NULL, NULL}, + {"access_positive", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t,access_positive), NULL, "yes"}, + {"base_filter", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,base_filter), NULL, "(objectclass=radiusprofile)"}, + {"default_profile", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,default_profile), NULL, NULL}, + {"profile_attribute", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,profile_attr), NULL, NULL}, { NULL, -1, 0, NULL, NULL } }; - /* * Group configuration */ static CONF_PARSER group_config[] = { /* - * Group checks. These could probably be done - * via dynamic xlat's. + * Group checks. These could probably be done via dynamic xlat's. */ - {"name_attribute", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,groupname_attr), NULL, "cn"}, - {"membership_filter", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,groupmemb_filter), NULL, - "(|(&(objectClass=GroupOfNames)(member=%{Ldap-UserDn}))" - "(&(objectClass=GroupOfUniqueNames)(uniquemember=%{Ldap-UserDn})))"}, - {"membership_attribute", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,groupmemb_attr), NULL, NULL}, - - + {"name_attribute", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,groupobj_name_attr), NULL, "cn"}, + {"membership_filter", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,groupobj_membership_filter), NULL, + "(|(&(objectClass=GroupOfNames)(member=%{Ldap-UserDn}))(&(objectClass=GroupOfUniqueNames)" + "(uniquemember=%{Ldap-UserDn})))"}, + {"membership_attribute", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,userobj_membership_attr), NULL, NULL}, { NULL, -1, 0, NULL, NULL } }; @@ -220,8 +88,7 @@ static CONF_PARSER group_config[] = { * Reference for accounting updates */ static const CONF_PARSER acct_section_config[] = { - {"reference", PW_TYPE_STRING_PTR, - offsetof(ldap_acct_section_t, reference), NULL, "."}, + {"reference", PW_TYPE_STRING_PTR, offsetof(ldap_acct_section_t, reference), NULL, "."}, {NULL, -1, 0, NULL, NULL} }; @@ -234,78 +101,58 @@ static CONF_PARSER option_config[] = { /* * Debugging flags to the server */ - {"ldap_debug", PW_TYPE_INTEGER, - offsetof(ldap_instance,ldap_debug), NULL, "0x0000"}, + {"ldap_debug", PW_TYPE_INTEGER, offsetof(ldap_instance_t,ldap_debug), NULL, "0x0000"}, - {"chase_referrals", PW_TYPE_BOOLEAN, - offsetof(ldap_instance,chase_referrals), NULL, NULL}, + {"chase_referrals", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t,chase_referrals), NULL, NULL}, - {"rebind", PW_TYPE_BOOLEAN, - offsetof(ldap_instance,rebind), NULL, NULL}, + {"rebind", PW_TYPE_BOOLEAN,offsetof(ldap_instance_t,rebind), NULL, NULL}, /* timeout on network activity */ - {"net_timeout", PW_TYPE_INTEGER, - offsetof(ldap_instance,net_timeout), NULL, "10"}, + {"net_timeout", PW_TYPE_INTEGER, offsetof(ldap_instance_t,net_timeout), NULL, "10"}, /* timeout for search results */ - {"timeout", PW_TYPE_INTEGER, - offsetof(ldap_instance,timeout), NULL, "20"}, + {"timeout", PW_TYPE_INTEGER, offsetof(ldap_instance_t,timeout), NULL, "20"}, /* allow server unlimited time for search (server-side limit) */ - {"timelimit", PW_TYPE_INTEGER, - offsetof(ldap_instance,timelimit), NULL, "20"}, + {"timelimit", PW_TYPE_INTEGER, offsetof(ldap_instance_t,timelimit), NULL, "20"}, #ifdef LDAP_OPT_X_KEEPALIVE_IDLE - {"idle", PW_TYPE_INTEGER, - offsetof(ldap_instance,keepalive_idle), NULL, "60"}, + {"idle", PW_TYPE_INTEGER, offsetof(ldap_instance_t,keepalive_idle), NULL, "60"}, #endif #ifdef LDAP_OPT_X_KEEPALIVE_PROBES - {"probes", PW_TYPE_INTEGER, - offsetof(ldap_instance,keepalive_probes), NULL, "3"}, + {"probes", PW_TYPE_INTEGER, offsetof(ldap_instance_t,keepalive_probes), NULL, "3"}, #endif #ifdef LDAP_OPT_ERROR_NUMBER - {"interval", PW_TYPE_INTEGER, - offsetof(ldap_instance,keepalive_interval), NULL, "30"}, + {"interval", PW_TYPE_INTEGER, offsetof(ldap_instance_t,keepalive_interval), NULL, "30"}, #endif { NULL, -1, 0, NULL, NULL } }; static const CONF_PARSER module_config[] = { - {"server", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,server), NULL, "localhost"}, - {"port", PW_TYPE_INTEGER, - offsetof(ldap_instance,port), NULL, "389"}, + {"server", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,server), NULL, "localhost"}, + {"port", PW_TYPE_INTEGER, offsetof(ldap_instance_t,port), NULL, "389"}, - {"password", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,password), NULL, ""}, - {"identity", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,login), NULL, ""}, + {"password", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,password), NULL, ""}, + {"identity", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,login), NULL, ""}, /* * DN's and filters. */ - {"basedn", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,basedn), NULL, "o=notexist"}, + {"basedn", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,basedn), NULL, "o=notexist"}, - {"filter", PW_TYPE_STRING_PTR, - offsetof(ldap_instance,filter), NULL, "(uid=%u)"}, + {"filter", PW_TYPE_STRING_PTR, offsetof(ldap_instance_t,userobj_filter), NULL, "(uid=%u)"}, - /* turn off the annoying warning if we don't expect a password */ - {"expect_password", PW_TYPE_BOOLEAN, - offsetof(ldap_instance,expect_password), NULL, "yes"}, - #ifdef WITH_EDIR /* support for eDirectory Universal Password */ {"edir", PW_TYPE_BOOLEAN, - offsetof(ldap_instance,edir), NULL, NULL}, /* NULL defaults to "no" */ + offsetof(ldap_instance_t,edir), NULL, NULL}, /* NULL defaults to "no" */ /* * Attempt to bind with the Cleartext password we got from eDirectory * Universal password for additional authorization checks. */ - {"edir_autz", PW_TYPE_BOOLEAN, - offsetof(ldap_instance,edir_autz), NULL, NULL}, /* NULL defaults to "no" */ + {"edir_autz", PW_TYPE_BOOLEAN, offsetof(ldap_instance_t,edir_autz), NULL, NULL}, /* NULL defaults to "no" */ #endif /* @@ -323,636 +170,28 @@ static const CONF_PARSER module_config[] = { {NULL, -1, 0, NULL, NULL} }; -typedef struct ldap_conn { - LDAP *handle; - int rebound; - int referred; - ldap_instance *inst; -} LDAP_CONN; - -typedef struct xlat_attrs { - const value_pair_map_t *maps; - const char *attrs[MAX_ATTRMAP]; -} xlat_attrs_t; - -typedef struct rlm_ldap_result { - char **values; - int count; -} rlm_ldap_result_t; - -typedef enum { - LDAP_PROC_SUCCESS = 0, - LDAP_PROC_ERROR = -1, - LDAP_PROC_RETRY = -2, - LDAP_PROC_NOTPERMITTED = -3, - LDAP_PROC_REJECT = -4 -} ldap_rcode_t; - -static ldap_rcode_t process_ldap_errno(ldap_instance *inst, - const LDAP_CONN *conn, - const char **error) -{ - int ldap_errno; - - *error = NULL; - - ldap_get_option(conn->handle, LDAP_OPT_ERROR_NUMBER, - &ldap_errno); - switch (ldap_errno) { - case LDAP_SUCCESS: - case LDAP_NO_SUCH_OBJECT: - return LDAP_PROC_SUCCESS; - - case LDAP_INSUFFICIENT_ACCESS: - *error = "Insufficient access. Check the identity and password " - "configuration directive"; - - return LDAP_PROC_NOTPERMITTED; - - case LDAP_UNWILLING_TO_PERFORM: - *error = "Server was unwilling to perform"; - - return LDAP_PROC_NOTPERMITTED; - - case LDAP_TIMEOUT: - exec_trigger(NULL, inst->cs, "modules.ldap.timeout", TRUE); - - *error = "Timed out while waiting for server to respond"; - - return LDAP_PROC_ERROR; - - case LDAP_FILTER_ERROR: - *error = "Bad search filter"; - - return LDAP_PROC_ERROR; - - case LDAP_TIMELIMIT_EXCEEDED: - exec_trigger(NULL, inst->cs, "modules.ldap.timeout", TRUE); - - *error = "Time limit exceeded"; - /* FALL-THROUGH */ - - case LDAP_BUSY: - case LDAP_UNAVAILABLE: - /* - * Reconnect. There's an issue with the socket - * or LDAP server. - */ - *error = ldap_err2string(ldap_errno); - - case LDAP_SERVER_DOWN: - return LDAP_PROC_RETRY; - - case LDAP_INVALID_CREDENTIALS: - case LDAP_CONSTRAINT_VIOLATION: - *error = ldap_err2string(ldap_errno); - - return LDAP_PROC_REJECT; - - case LDAP_OPERATIONS_ERROR: - *error = "Please set 'chase_referrals=yes' and 'rebind=yes'. " - "See the ldap module configuration for details."; - /* FALL-THROUGH */ - - default: - *error = ldap_err2string(ldap_errno); - - return LDAP_PROC_ERROR; - } -} - - -static int ldap_bind_wrapper(REQUEST *request, LDAP_CONN **pconn, - const char *user, const char *password, - int retry) -{ - int rcode, msg_id; - int module_rcode = RLM_MODULE_OK; - LDAP_CONN *conn = *pconn; - ldap_instance *inst = conn->inst; - LDAPMessage *result = NULL; - const char *error = NULL; - char *ext_error = NULL; - struct timeval tv; - -retry: - msg_id = ldap_bind(conn->handle, user, password, LDAP_AUTH_SIMPLE); - if (msg_id < 0) goto get_error; - - DEBUG3("rlm_ldap (%s): Waiting for bind result...", inst->xlat_name); - - tv.tv_sec = inst->timeout; - tv.tv_usec = 0; - - rcode = ldap_result(conn->handle, msg_id, 1, &tv, &result); - if (rcode > 0) { - rcode = ldap_parse_result(conn->handle, result, NULL, NULL, - &ext_error, NULL, NULL, 1); - } - -get_error: - switch (process_ldap_errno(inst, conn, &error)) - { - case LDAP_PROC_SUCCESS: - break; - case LDAP_PROC_NOTPERMITTED: - if (request) { - RDEBUGE("Bind was not permitted (%s): %s", error, - ext_error ? ext_error : - "no additional information"); - } else { - radlog(L_ERR, "rlm_ldap (%s): Bind was not permitted " - "(%s): %s", inst->xlat_name, - error, ext_error ? ext_error : - "no additional information"); - } - - module_rcode = RLM_MODULE_USERLOCK; - - break; - case LDAP_PROC_REJECT: - if (request) { - RDEBUGE("Bind credentials incorrect (%s): %s", error, - ext_error ? ext_error : - "no additional information"); - } else { - radlog(L_ERR, "rlm_ldap (%s): Bind credentials " - "incorrect (%s): %s", inst->xlat_name, - error, ext_error ? ext_error : - "no additional information"); - } - - module_rcode = RLM_MODULE_REJECT; - - break; - case LDAP_PROC_ERROR: - module_rcode = RLM_MODULE_FAIL; -error: -#ifdef HAVE_LDAP_INITIALIZE - if (inst->is_url) { - radlog(L_ERR, "rlm_ldap (%s): bind " - "with %s to %s failed: %s", - inst->xlat_name, user, - inst->server, error); - } else -#endif - { - radlog(L_ERR, "rlm_ldap (%s): bind " - "with %s to %s:%d failed: %s", - inst->xlat_name, user, - inst->server, inst->port, error); - } - - break; - case LDAP_PROC_RETRY: - if (retry) { - radlog(L_ERR, "rlm_ldap (%s): bind " - "with %s to %s:%d failed, reconnecting: %s", - inst->xlat_name, user, - inst->server, inst->port, error); - ldap_memfree(ext_error); - ext_error = NULL; - - *pconn = fr_connection_reconnect(inst->pool, *pconn); - if (*pconn) goto retry; - } else goto error; - - module_rcode = RLM_MODULE_FAIL; - break; - } - - ldap_memfree(ext_error); - - return module_rcode; /* caller closes the connection */ -} - -#if LDAP_SET_REBIND_PROC_ARGS == 3 -/* - * Rebind && chase referral stuff - */ -static int ldap_rebind(LDAP *handle, LDAP_CONST char *url, - UNUSED ber_tag_t request, UNUSED ber_int_t msgid, - void *ctx ) -{ - int rcode, ldap_errno; - LDAP_CONN *conn = ctx; - - conn->referred = TRUE; - conn->rebound = TRUE; /* not really, but oh well... */ - rad_assert(handle == conn->handle); - - DEBUG("rlm_ldap (%s): Rebinding to URL %s", conn->inst->xlat_name, url); - - - rcode = ldap_bind_wrapper(NULL, &conn, conn->inst->login, - conn->inst->password, FALSE); - - if (rcode == RLM_MODULE_OK) { - return LDAP_SUCCESS; - } - - ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); - - return ldap_errno; -} -#endif - -/** Create and return a new connection - * This function is probably too big. - */ -static void *ldap_conn_create(void *ctx) -{ - int module_rcode; - int ldap_errno, ldap_version; - struct timeval tv; - ldap_instance *inst = ctx; - LDAP *handle = NULL; - LDAP_CONN *conn = NULL; - -#ifdef HAVE_LDAP_INITIALIZE - if (inst->is_url) { - DEBUG("rlm_ldap (%s): Connecting to %s", inst->xlat_name, - inst->server); - - ldap_errno = ldap_initialize(&handle, inst->server); - if (ldap_errno != LDAP_SUCCESS) { - radlog(L_ERR, "rlm_ldap (%s): ldap_initialize() " - "failed: %s", - inst->xlat_name, ldap_err2string(ldap_errno)); - goto conn_fail; - } - } else -#endif - { - DEBUG("rlm_ldap (%s): Connecting to %s:%d", inst->xlat_name, - inst->server, inst->port); - - handle = ldap_init(inst->server, inst->port); - if (!handle) { - radlog(L_ERR, "rlm_ldap (%s): ldap_init() failed", - inst->xlat_name); - conn_fail: - if (handle) ldap_unbind_s(handle); - return NULL; - } - } - - /* - * We now have a connection structure, but no actual TCP connection. - * - * Set a bunch of LDAP options, using common code. - */ -#define do_ldap_option(_option, _name, _value) \ - if (ldap_set_option(handle, _option, _value) != LDAP_OPT_SUCCESS) { \ - ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); \ - radlog(L_ERR, "rlm_ldap (%s): Could not set %s: %s", \ - inst->xlat_name, _name, ldap_err2string(ldap_errno)); \ - } - - if (inst->ldap_debug) { - do_ldap_option(LDAP_OPT_DEBUG_LEVEL, "ldap_debug", - &(inst->ldap_debug)); - } - - /* - * Leave "chase_referrals" unset to use the OpenLDAP - * default. - */ - if (inst->chase_referrals != 2) { - if (inst->chase_referrals) { - do_ldap_option(LDAP_OPT_REFERRALS, "chase_referrals", - LDAP_OPT_ON); - - if (inst->rebind == 1) { -#if LDAP_SET_REBIND_PROC_ARGS == 3 - ldap_set_rebind_proc(handle, ldap_rebind, inst); -#else - DEBUGW("The flag 'rebind = yes' is not supported by the system LDAP library. Ignoring."); -#endif - } - } else { - do_ldap_option(LDAP_OPT_REFERRALS, "chase_referrals", - LDAP_OPT_OFF); - } - } - - tv.tv_sec = inst->net_timeout; - tv.tv_usec = 0; - do_ldap_option(LDAP_OPT_NETWORK_TIMEOUT, "net_timeout", &tv); - - do_ldap_option(LDAP_OPT_TIMELIMIT, "timelimit", &(inst->timelimit)); - - ldap_version = LDAP_VERSION3; - do_ldap_option(LDAP_OPT_PROTOCOL_VERSION, "ldap_version", - &ldap_version); - -#ifdef LDAP_OPT_X_KEEPALIVE_IDLE - do_ldap_option(LDAP_OPT_X_KEEPALIVE_IDLE, "keepalive idle", - &(inst->keepalive_idle)); -#endif - -#ifdef LDAP_OPT_X_KEEPALIVE_PROBES - do_ldap_option(LDAP_OPT_X_KEEPALIVE_PROBES, "keepalive probes", - &(inst->keepalive_probes)); -#endif - -#ifdef LDAP_OPT_X_KEEPALIVE_INTERVAL - do_ldap_option(LDAP_OPT_X_KEEPALIVE_INTERVAL, "keepalive interval", - &(inst->keepalive_interval)); -#endif - -#ifdef HAVE_LDAP_START_TLS - /* - * Set all of the TLS options - */ - if (inst->tls_mode) { - do_ldap_option(LDAP_OPT_X_TLS, "tls_mode", &(inst->tls_mode)); - } - -#define maybe_ldap_option(_option, _name, _value) \ - if (_value) do_ldap_option(_option, _name, _value) - - maybe_ldap_option(LDAP_OPT_X_TLS_CACERTFILE, - "cacertfile", inst->tls_cacertfile); - maybe_ldap_option(LDAP_OPT_X_TLS_CACERTDIR, - "cacertdir", inst->tls_cacertdir); - -#ifdef HAVE_LDAP_INT_TLS_CONFIG - if (ldap_int_tls_config(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, - (inst->tls_require_cert)) != LDAP_OPT_SUCCESS) { - ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno); - radlog(L_ERR, "rlm_ldap (%s): could not set " - "LDAP_OPT_X_TLS_REQUIRE_CERT option to %s: %s", - inst->xlat_name, - inst->tls_require_cert, - ldap_err2string(ldap_errno)); - } -#endif - - maybe_ldap_option(LDAP_OPT_X_TLS_CERTFILE, - "certfile", inst->tls_certfile); - maybe_ldap_option(LDAP_OPT_X_TLS_KEYFILE, - "keyfile", inst->tls_keyfile); - maybe_ldap_option(LDAP_OPT_X_TLS_RANDOM_FILE, - "randfile", inst->tls_randfile); - - /* - * And finally start the TLS code. - */ - if (inst->start_tls && (inst->port != 636)) { - ldap_errno = ldap_start_tls_s(handle, NULL, NULL); - if (ldap_errno != LDAP_SUCCESS) { - ldap_get_option(handle, LDAP_OPT_ERROR_NUMBER, - &ldap_errno); - radlog(L_ERR, "rlm_ldap (%s): could not start TLS: %s", - inst->xlat_name, - ldap_err2string(ldap_errno)); - goto conn_fail; - } - } -#endif /* HAVE_LDAP_START_TLS */ - - conn = talloc_zero(ctx, LDAP_CONN); - conn->inst = inst; - conn->handle = handle; - conn->rebound = FALSE; - conn->referred = FALSE; - - module_rcode = ldap_bind_wrapper(NULL, &conn, inst->login, - inst->password, FALSE); - if (module_rcode != RLM_MODULE_OK) { - goto conn_fail; - } - - return conn; -} - - -/** Close and delete a connection - * - */ -static int ldap_conn_delete(UNUSED void *ctx, void *connection) -{ - LDAP_CONN *conn = connection; - - ldap_unbind_s(conn->handle); - talloc_free(conn); - - return 0; -} - - -/** Gets an LDAP socket from the connection pool - * - */ -static LDAP_CONN *ldap_get_socket(ldap_instance *inst) -{ - LDAP_CONN *conn; - - conn = fr_connection_get(inst->pool); - if (!conn) { - radlog(L_ERR, "rlm_ldap (%s): all ldap connections are in use", - inst->xlat_name); - return NULL; - } - - return conn; -} - -/** Frees an LDAP socket back to the connection pool - * - */ -static void ldap_release_socket(ldap_instance *inst, LDAP_CONN *conn) -{ - /* - * Could have already been free'd due to a previous error. - */ - if (!conn) return; - - /* - * We chased a referral to another server. - * - * This connection is no longer part of the pool which is - * connected to and bound to the configured server. - * Close it. - * - * Note that we do NOT close it if it was bound to - * another user. Instead, we let the next caller do the - * rebind. - */ - if (conn->referred) { - fr_connection_del(inst->pool, conn); - return; - } - - fr_connection_release(inst->pool, conn); - return; -} - - -/* Converts "bad" strings into ones which are safe for LDAP - * - */ -static size_t ldap_escape_func(UNUSED REQUEST *request, char *out, - size_t outlen, const char *in, UNUSED void *arg) -{ - static const char encode[] = ",+\"\\<>;*=()"; - static const char hextab[] = "0123456789abcdef"; - size_t left = outlen; - - if (*in && ((*in == ' ') || (*in == '#'))) { - goto encode; - } - - while (*in) { - /* - * Encode unsafe characters. - */ - if (memchr(encode, *in, sizeof(encode) - 1)) { - encode: - - /* - * Only 3 or less bytes available. - */ - if (left <= 3) break; - - *out++ = '\\'; - *out++ = hextab[(*in >> 4) & 0x0f]; - *out++ = hextab[*in & 0x0f]; - in++; - left -= 3; - - continue; - } - - if (left <= 1) break; - - /* - * Doesn't need encoding - */ - *out++ = *in++; - left--; - } - - *out = '\0'; - - return outlen - left; -} - -/** Do a search and get a response - * - */ -static int perform_search(ldap_instance *inst, REQUEST *request, - LDAP_CONN **pconn, const char *search_basedn, - int scope, const char *filter, - const char * const *attrs, LDAPMessage **presult) -{ - int ldap_errno; - int count = 0; - struct timeval tv; - const char *error = NULL; - - /* - * OpenLDAP library doesn't declare attrs array as const, but - * it really should be *sigh*. - */ - char **search_attrs; - memcpy(&search_attrs, &attrs, sizeof(attrs)); - - *presult = NULL; - - /* - * Do all searches as the default admin user. - */ - if ((*pconn)->rebound) { - ldap_errno = ldap_bind_wrapper(request, pconn, inst->login, - inst->password, TRUE); - if (ldap_errno != RLM_MODULE_OK) { - return -1; - } - - rad_assert(*pconn); - (*pconn)->rebound = FALSE; - } - - tv.tv_sec = inst->timeout; - tv.tv_usec = 0; - RDEBUG2("Performing search in '%s' with filter '%s'", - search_basedn ? search_basedn : "(null)" , - filter); - -retry: - ldap_errno = ldap_search_ext_s((*pconn)->handle, search_basedn, scope, - filter, search_attrs, 0, NULL, NULL, - &tv, 0, presult); - if (ldap_errno != LDAP_SUCCESS) { - ldap_msgfree(*presult); - switch (process_ldap_errno(inst, *pconn, &error)) - { - case LDAP_PROC_SUCCESS: - break; - case LDAP_PROC_RETRY: - radlog(L_ERR, "rlm_ldap (%s): Failed " - "performing search, reconnecting: %s", - inst->xlat_name, error); - - *pconn = fr_connection_reconnect(inst->pool, - *pconn); - if (*pconn) goto retry; - - return -1; - default: - radlog(L_ERR, "rlm_ldap (%s): Failed " - "performing search: %s", - inst->xlat_name, error); - - return -1; - } - } - - count = ldap_count_entries((*pconn)->handle, *presult); - if (count == 0) { - ldap_msgfree(*presult); - RDEBUGE("Search returned no results"); - - return -2; - } - - if (count != 1) { - ldap_msgfree(*presult); - RDEBUGE("Got ambiguous search result (%d results)", count); - - return -2; - } - - return 0; -} - /** Expand an LDAP URL into a query, and return a string result from that query. * */ static size_t ldap_xlat(void *instance, REQUEST *request, const char *fmt, char *out, size_t freespace) { - int rcode; + ldap_rcode_t status; size_t length = 0; - ldap_instance *inst = instance; + ldap_instance_t *inst = instance; LDAPURLDesc *ldap_url; LDAPMessage *result = NULL; LDAPMessage *entry = NULL; char **vals; - LDAP_CONN *conn; + ldap_handle_t *conn; int ldap_errno; const char *url; const char **attrs; - char buffer[MAX_FILTER_STR_LEN]; + char buffer[LDAP_MAX_FILTER_STR_LEN]; if (strchr(fmt, '%') != NULL) { - if (!radius_xlat(buffer, sizeof(buffer), fmt, request, - ldap_escape_func, NULL)) { - radlog(L_ERR, - "rlm_ldap (%s): Unable to create LDAP URL", - inst->xlat_name); + if (!radius_xlat(buffer, sizeof(buffer), fmt, request, rlm_ldap_escape_func, NULL)) { + RDEBUGE("Unable to create LDAP URL"); return 0; } url = buffer; @@ -961,14 +200,12 @@ static size_t ldap_xlat(void *instance, REQUEST *request, const char *fmt, } if (!ldap_is_ldap_url(url)) { - radlog(L_ERR, "rlm_ldap (%s): String passed does not look " - "like an LDAP URL", inst->xlat_name); + RDEBUGE("String passed does not look like an LDAP URL"); return 0; } if (ldap_url_parse(url, &ldap_url)){ - radlog(L_ERR, "rlm_ldap (%s): Parsing LDAP URL failed", - inst->xlat_name); + RDEBUGE("Parsing LDAP URL failed"); return 0; } @@ -979,55 +216,50 @@ static size_t ldap_xlat(void *instance, REQUEST *request, const char *fmt, !*ldap_url->lud_attrs[0] || (strcmp(ldap_url->lud_attrs[0], "*") == 0) || ldap_url->lud_attrs[1]) { - radlog(L_ERR, "rlm_ldap (%s): Bad attributes list in LDAP " - "URL. URL must specify exactly one attribute to " - "retrieve", - inst->xlat_name); + RDEBUGE("Bad attributes list in LDAP URL. " + "URL must specify exactly one attribute to " + "retrieve"); goto free_urldesc; } - if (ldap_url->lud_host && - ((strncmp(inst->server, ldap_url->lud_host, - strlen(inst->server)) != 0) || + if (ldap_url->lud_host && + ((strncmp(inst->server, ldap_url->lud_host, strlen(inst->server)) != 0) || (ldap_url->lud_port != inst->port))) { - RDEBUG("Requested server/port is \"%s:%i\"", ldap_url->lud_host, - inst->port); + RDEBUG("Requested server/port is \"%s:%i\"", ldap_url->lud_host, inst->port); goto free_urldesc; } - conn = ldap_get_socket(inst); + conn = rlm_ldap_get_socket(inst, request); if (!conn) goto free_urldesc; memcpy(&attrs, &ldap_url->lud_attrs, sizeof(attrs)); - rcode = perform_search(inst, request, &conn, ldap_url->lud_dn, - ldap_url->lud_scope, ldap_url->lud_filter, attrs, - &result); - if (rcode < 0) { - if (rcode == -2) { - RDEBUG("Search returned not found", inst->xlat_name); + status = rlm_ldap_search(inst, request, &conn, ldap_url->lud_dn, ldap_url->lud_scope, ldap_url->lud_filter, + attrs, &result); + switch (status) { + case LDAP_PROC_SUCCESS: + break; + case LDAP_PROC_NO_RESULT: + RDEBUG("Search returned not found"); + default: goto free_socket; - } - - goto free_socket; } + rad_assert(conn); + rad_assert(result); + entry = ldap_first_entry(conn->handle, result); if (!entry) { - ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, - &ldap_errno); - radlog(L_ERR, "rlm_ldap (%s): Failed retrieving entry: %s", - inst->xlat_name, - ldap_err2string(ldap_errno)); + ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); + RDEBUGE("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); goto free_result; } vals = ldap_get_values(conn->handle, entry, ldap_url->lud_attrs[0]); if (!vals) { - RDEBUG("No \"%s\" attributes found in specified object", - inst->xlat_name, ldap_url->lud_attrs[0]); + RDEBUG("No \"%s\" attributes found in specified object", ldap_url->lud_attrs[0]); goto free_result; } @@ -1044,125 +276,44 @@ free_vals: free_result: ldap_msgfree(result); free_socket: - ldap_release_socket(inst, conn); + rlm_ldap_release_socket(inst, conn); free_urldesc: ldap_free_urldesc(ldap_url); return length; } - -static char *get_userdn(LDAP_CONN **pconn, REQUEST *request, - rlm_rcode_t *module_rcode) -{ - int rcode; - VALUE_PAIR *vp; - ldap_instance *inst = (*pconn)->inst; - LDAPMessage *result, *entry; - int ldap_errno; - static char firstattr[] = "uid"; - char *user_dn; - const char *attrs[] = {firstattr, NULL}; - char filter[MAX_FILTER_STR_LEN]; - char basedn[MAX_FILTER_STR_LEN]; - - *module_rcode = RLM_MODULE_FAIL; - - vp = pairfind(request->config_items, PW_LDAP_USERDN, 0, TAG_ANY); - if (vp) { - *module_rcode = RLM_MODULE_OK; - return vp->vp_strvalue; - } - - if (!radius_xlat(filter, sizeof(filter), inst->filter, - request, ldap_escape_func, NULL)) { - radlog(L_ERR, "rlm_ldap (%s): Unable to create filter", - inst->xlat_name); - *module_rcode = RLM_MODULE_INVALID; - return NULL; - } - - if (!radius_xlat(basedn, sizeof(basedn), inst->basedn, - request, ldap_escape_func, NULL)) { - radlog(L_ERR, "rlm_ldap (%s): Unable to create basedn", - inst->xlat_name); - *module_rcode = RLM_MODULE_INVALID; - return NULL; - } - - rcode = perform_search(inst, request, pconn, basedn, LDAP_SCOPE_SUBTREE, - filter, attrs, &result); - if (rcode < 0) { - if (rcode == -2) { - *module_rcode = RLM_MODULE_NOTFOUND; - } - - return NULL; - } - - if ((entry = ldap_first_entry((*pconn)->handle, result)) == NULL) { - ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, - &ldap_errno); - radlog(L_ERR, "rlm_ldap (%s): Failed retrieving entry: %s", - inst->xlat_name, - ldap_err2string(ldap_errno)); - ldap_msgfree(result); - return NULL; - } - - if ((user_dn = ldap_get_dn((*pconn)->handle, entry)) == NULL) { - ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, - &ldap_errno); - radlog(L_ERR, "rlm_ldap (%s): ldap_get_dn() failed: %s", - inst->xlat_name, - ldap_err2string(ldap_errno)); - - ldap_msgfree(result); - return NULL; - } - - vp = pairmake("LDAP-UserDn", user_dn, T_OP_EQ); - if (!vp) { - ldap_memfree(user_dn); - ldap_msgfree(result); - return NULL; - } - - *module_rcode = RLM_MODULE_OK; - - pairadd(&request->config_items, vp); - ldap_memfree(user_dn); - ldap_msgfree(result); - - return vp->vp_strvalue; -} - - /** Perform LDAP-Group comparison checking * + * Attempts to match users to groups using a variety of methods. + * + * @param instance of the rlm_ldap module. + * @param request Current request. + * @param thing Unknown. + * @param check Which group to check for user membership. + * @param check_pairs Unknown. + * @param reply_pairs Unknown. + * @return 1 on failure (or if the user is not a member), else 0. */ -static int ldap_groupcmp(void *instance, REQUEST *request, - UNUSED VALUE_PAIR *thing, VALUE_PAIR *check, - UNUSED VALUE_PAIR *check_pairs, - UNUSED VALUE_PAIR **reply_pairs) +static int rlm_ldap_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *thing, VALUE_PAIR *check, + UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs) { - ldap_instance *inst = instance; - int i, rcode, found; - rlm_rcode_t module_rcode; + ldap_instance_t *inst = instance; + rlm_rcode_t rcode; + ldap_rcode_t status; + int i, found; LDAPMessage *result = NULL; LDAPMessage *entry = NULL; int ldap_errno; int check_is_dn = FALSE, value_is_dn = FALSE; - static char firstattr[] = "dn"; - const char *attrs[] = {firstattr, NULL}; char **vals; - const char *group_attrs[] = {inst->groupmemb_attr, NULL}; - LDAP_CONN *conn; - char *user_dn; + const char *group_attrs[] = {inst->userobj_membership_attr, NULL}; + ldap_handle_t *conn; + const char *user_dn; - char gr_filter[MAX_FILTER_STR_LEN]; - char filter[MAX_FILTER_STR_LEN]; - char basedn[MAX_FILTER_STR_LEN]; + char gr_filter[LDAP_MAX_FILTER_STR_LEN]; + char filter[LDAP_MAX_FILTER_STR_LEN]; + char basedn[LDAP_MAX_FILTER_STR_LEN]; RDEBUG("Searching for user in group \"%s\"", check->vp_strvalue); @@ -1171,116 +322,109 @@ static int ldap_groupcmp(void *instance, REQUEST *request, return 1; } - conn = ldap_get_socket(inst); + conn = rlm_ldap_get_socket(inst, request); if (!conn) return 1; /* * This is used in the default membership filter. */ - user_dn = get_userdn(&conn, request, &module_rcode); + user_dn = rlm_ldap_find_user(inst, request, &conn, NULL, NULL, &rcode); if (!user_dn) { - ldap_release_socket(inst, conn); + rlm_ldap_release_socket(inst, conn); return 1; } - - if (!inst->groupmemb_filter) goto check_attr; + + rad_assert(conn); + + if (!inst->groupobj_membership_filter) goto check_attr; if (!radius_xlat(gr_filter, sizeof(gr_filter), - inst->groupmemb_filter, request, ldap_escape_func, + inst->groupobj_membership_filter, request, rlm_ldap_escape_func, NULL)) { - radlog(L_ERR, "rlm_ldap (%s): Failed creating group filter", - inst->xlat_name); + RDEBUGE("Failed creating group filter"); + return 1; } /* * If it's a DN, use that. */ - check_is_dn = strchr(check->vp_strvalue,',') == NULL ? FALSE : TRUE; - + check_is_dn = rlm_ldap_is_dn(check->vp_strvalue); if (check_is_dn) { strlcpy(filter, gr_filter, sizeof(filter)); strlcpy(basedn, check->vp_strvalue, sizeof(basedn)); } else { snprintf(filter, sizeof(filter), "(&(%s=%s)%s)", - inst->groupname_attr, + inst->groupobj_name_attr, check->vp_strvalue, gr_filter); /* - * get_userdn does this, too. Oh well. + * rlm_ldap_find_user does this, too. Oh well. */ - if (!radius_xlat(basedn, sizeof(basedn), inst->basedn, - request, ldap_escape_func, NULL)) { - radlog(L_ERR, "rlm_ldap (%s): Failed creating basedn", - inst->xlat_name); + if (!radius_xlat(basedn, sizeof(basedn), inst->basedn, request, rlm_ldap_escape_func, NULL)) { + RDEBUGE("Failed creating basedn"); + return 1; } } - rcode = perform_search(inst, request, &conn, basedn, LDAP_SCOPE_SUBTREE, - filter, attrs, &result); - if (rcode == 0) { - ldap_release_socket(inst, conn); - ldap_msgfree(result); - - RDEBUG("User found in group object"); - - return 0; - } - - if (rcode == -1) { - ldap_release_socket(inst, conn); - return 1; + status = rlm_ldap_search(inst, request, &conn, basedn, LDAP_SCOPE_SUBTREE, filter, NULL, NULL); + switch (status) { + case LDAP_PROC_SUCCESS: + RDEBUG("User found in group object"); + found = TRUE; + goto finish; + case LDAP_PROC_NO_RESULT: + RDEBUG("Search returned not found"); + goto check_attr; + default: + goto finish; } - - /* else the search returned -2, for "not found" */ + + rad_assert(conn); + rad_assert(result); /* * Else the search returned NOTFOUND. See if we're * configured to search for group membership using user * object attribute. */ - if (!inst->groupmemb_attr) { - ldap_release_socket(inst, conn); - RDEBUG("Group object \"%s\" not found, or user is not a member", - check->vp_strvalue); + if (!inst->userobj_membership_attr) { + rlm_ldap_release_socket(inst, conn); + RDEBUG("Group object \"%s\" not found, or user is not a member", check->vp_strvalue); return 1; } check_attr: - RDEBUG2("Checking user object membership (%s) attributes", - inst->groupmemb_attr); + RDEBUG2("Checking user object membership (%s) attributes", inst->userobj_membership_attr); snprintf(filter ,sizeof(filter), "(objectclass=*)"); - rcode = perform_search(inst, request, &conn, user_dn, LDAP_SCOPE_BASE, - filter, group_attrs, &result); - if (rcode < 0) { - if (rcode == -2) { - RDEBUG("Can't check membership attributes, user object " - "not found"); - } - ldap_release_socket(inst, conn); - return 1; + status = rlm_ldap_search(inst, request, &conn, user_dn, LDAP_SCOPE_BASE, filter, group_attrs, &result); + switch (status) { + case LDAP_PROC_SUCCESS: + break; + case LDAP_PROC_NO_RESULT: + RDEBUG("Can't check membership attributes, user object not found"); + default: + rlm_ldap_release_socket(inst, conn); + return 1; } entry = ldap_first_entry(conn->handle, result); if (!entry) { - ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, - &ldap_errno); - radlog(L_ERR, "rlm_ldap (%s): Failed retrieving entry: %s", - inst->xlat_name, - ldap_err2string(ldap_errno)); + ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); + RDEBUGE("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); - ldap_release_socket(inst, conn); + rlm_ldap_release_socket(inst, conn); ldap_msgfree(result); return 1; } - vals = ldap_get_values(conn->handle, entry, inst->groupmemb_attr); + vals = ldap_get_values(conn->handle, entry, inst->userobj_membership_attr); if (!vals) { RDEBUG("No group membership attribute(s) found in user object"); - ldap_release_socket(inst, conn); + rlm_ldap_release_socket(inst, conn); ldap_msgfree(result); return 1; } @@ -1291,9 +435,7 @@ check_attr: */ found = FALSE; for (i = 0; i < ldap_count_values(vals); i++) { - LDAPMessage *gr_result = NULL; - - value_is_dn = strchr(vals[i], ',') == NULL ? FALSE : TRUE; + value_is_dn = rlm_ldap_is_dn(vals[i]); RDEBUG2("Processing group membership value \"%s\"", vals[i]); @@ -1302,8 +444,7 @@ check_attr: */ if (!check_is_dn && !value_is_dn) { if (strcmp(vals[i], check->vp_strvalue) == 0){ - RDEBUG("User found (membership value matches " - "check value)"); + RDEBUG("User found (membership value matches check value)"); found = TRUE; break; @@ -1317,8 +458,7 @@ check_attr: */ if (check_is_dn && value_is_dn) { if (strcasecmp(vals[i], check->vp_strvalue) == 0){ - RDEBUG("User found (membership DN matches " - "check DN)"); + RDEBUG("User found (membership DN matches check DN)"); found = TRUE; break; @@ -1336,48 +476,34 @@ check_attr: /* * We have a value which is a DN, and a check item which * specifies the name of a group, search using the value - * DN for the group, and see if it has a groupname_attr + * DN for the group, and see if it has a groupobj_name_attr * which matches our check val. */ RDEBUG2("Searching with membership DN and group name"); - snprintf(filter,sizeof(filter), "(%s=%s)", - inst->groupname_attr, check->vp_strvalue); + snprintf(filter,sizeof(filter), "(%s=%s)", inst->groupobj_name_attr, check->vp_strvalue); - rcode = perform_search(inst, request, &conn, vals[i], - LDAP_SCOPE_BASE, filter, attrs, - &gr_result); - - ldap_msgfree(gr_result); - - /* Error occurred */ - if (rcode == -1) { - ldap_value_free(vals); - ldap_msgfree(result); - ldap_release_socket(inst, conn); - return 1; - } - - /* - * Either the group DN wasn't found, or it didn't have the - * correct name. Continue looping over the attributes. - */ - if (rcode == -2) { - ldap_msgfree(gr_result); - continue; + status = rlm_ldap_search(inst, request, &conn, vals[i], LDAP_SCOPE_BASE, filter, NULL, NULL); + switch (status) { + case LDAP_PROC_SUCCESS: + found = TRUE; + RDEBUG("User found (group name in membership DN matches check value)"); + + break; + case LDAP_PROC_NO_RESULT: + continue; + default: + goto finish; } - found = TRUE; - - RDEBUG("User found (group name in membership DN matches check " - "value)"); - break; } + finish: + ldap_value_free(vals); ldap_msgfree(result); - ldap_release_socket(inst, conn); + rlm_ldap_release_socket(inst, conn); if (!found) { RDEBUG("User is not a member of specified group"); @@ -1392,7 +518,7 @@ check_attr: */ static int ldap_detach(void *instance) { - ldap_instance *inst = instance; + ldap_instance_t *inst = instance; fr_connection_pool_delete(inst->pool); @@ -1403,9 +529,14 @@ static int ldap_detach(void *instance) return 0; } -static int parse_sub_section(CONF_SECTION *parent, - ldap_instance *inst, - ldap_acct_section_t **config, +/** Parse an accounting sub section. + * + * Allocate a new ldap_acct_section_t and write the config data into it. + * + * @param[in] parent of the config section. + * @param[out] + */ +static int parse_sub_section(ldap_instance_t *inst, CONF_SECTION *parent, ldap_acct_section_t **config, rlm_components_t comp) { CONF_SECTION *cs; @@ -1414,81 +545,21 @@ static int parse_sub_section(CONF_SECTION *parent, cs = cf_section_sub_find(parent, name); if (!cs) { - radlog(L_INFO, "rlm_ldap (%s): Couldn't find configuration for " - "%s, will return NOOP for calls from this section", - inst->xlat_name, name); + radlog(L_INFO, "rlm_ldap (%s): Couldn't find configuration for %s, will return NOOP for calls " + "from this section", inst->xlat_name, name); return 0; - } - - *config = talloc_zero(inst, ldap_acct_section_t); - if (cf_section_parse(cs, *config, acct_section_config) < 0) { - radlog(L_ERR, "rlm_ldap (%s): Failed parsing configuration for " - "section %s", inst->xlat_name, name); - return -1; - } - - (*config)->cs = cs; - - return 0; -} - -static int ldap_map_verify(ldap_instance *inst, value_pair_map_t **head) -{ - value_pair_map_t *map; + } - if (radius_attrmap(inst->cs, head, PAIR_LIST_REPLY, - PAIR_LIST_REQUEST, MAX_ATTRMAP) < 0) { + *config = talloc_zero(inst, ldap_acct_section_t); + if (cf_section_parse(cs, *config, acct_section_config) < 0) { + LDAP_ERR("Failed parsing configuration for section %s", name); + return -1; } - /* - * Attrmap only performs some basic validation checks, we need - * to do rlm_ldap specific checks here. - */ - for (map = *head; map != NULL; map = map->next) { - if (map->dst->type != VPT_TYPE_ATTR) { - cf_log_err(map->ci, "Left operand must be an " - "attribute ref"); - - return -1; - } - - if (map->src->type == VPT_TYPE_LIST) { - cf_log_err(map->ci, "Right operand must not be " - "a list"); - - return -1; - } - - switch (map->src->type) - { - /* - * Only =, :=, += and -= operators are supported for - * cache entries. - */ - case VPT_TYPE_LITERAL: - case VPT_TYPE_XLAT: - case VPT_TYPE_ATTR: - switch (map->op) { - case T_OP_SET: - case T_OP_EQ: - case T_OP_SUB: - case T_OP_ADD: - break; - default: - cf_log_err(map->ci, "Operator \"%s\" not " - "allowed for %s values", - fr_int2str(fr_tokens, map->op, - "¿unknown?"), - fr_int2str(vpt_types, map->src->type, - "¿unknown?")); - return -1; - } - default: - break; - } - } + (*config)->cs = cs; + return 0; } @@ -1498,9 +569,9 @@ static int ldap_map_verify(ldap_instance *inst, value_pair_map_t **head) */ static int ldap_instantiate(CONF_SECTION * conf, void **instance) { - ldap_instance *inst; + ldap_instance_t *inst; - *instance = inst = talloc_zero(conf, ldap_instance); + *instance = inst = talloc_zero(conf, ldap_instance_t); if (!inst) return -1; inst->cs = conf; @@ -1517,26 +588,21 @@ static int ldap_instantiate(CONF_SECTION * conf, void **instance) * If the configuration parameters can't be parsed, then fail. */ if ((cf_section_parse(conf, inst, module_config) < 0) || - (parse_sub_section(conf, inst, - &inst->accounting, - RLM_COMPONENT_ACCT) < 0) || - (parse_sub_section(conf, inst, - &inst->postauth, - RLM_COMPONENT_POST_AUTH) < 0)) { - radlog(L_ERR, "rlm_ldap (%s): Failed parsing configuration", - inst->xlat_name); + (parse_sub_section(inst, conf, &inst->accounting, RLM_COMPONENT_ACCT) < 0) || + (parse_sub_section(inst, conf, &inst->postauth, RLM_COMPONENT_POST_AUTH) < 0)) { + LDAP_ERR("Failed parsing configuration"); + goto error; } - if (inst->server == NULL) { - radlog(L_ERR, "rlm_ldap (%s): Missing 'server' directive", - inst->xlat_name); + if (!inst->server) { + LDAP_ERR("Missing 'server' directive"); + goto error; } /* - * Check for URLs. If they're used and the library doesn't - * support them, then complain. + * Check for URLs. If they're used and the library doesn't support them, then complain. */ inst->is_url = 0; if (ldap_is_ldap_url(inst->server)) { @@ -1544,14 +610,14 @@ static int ldap_instantiate(CONF_SECTION * conf, void **instance) inst->is_url = 1; inst->port = 0; #else - radlog(L_ERR, "rlm_ldap (%s): 'server' directive is in URL " - "form but ldap_initialize() is not available", - inst->xlat_name); + LDAP_ERR("'server' directive is in URL form but ldap_initialize() is not available"); goto error; #endif } - /* workaround for servers which support LDAPS but not START TLS */ + /* + * Workaround for servers which support LDAPS but not START TLS + */ if (inst->port == LDAPS_PORT || inst->tls_mode) { inst->tls_mode = LDAP_OPT_X_TLS_HARD; } else { @@ -1560,14 +626,13 @@ static int ldap_instantiate(CONF_SECTION * conf, void **instance) #if LDAP_SET_REBIND_PROC_ARGS != 3 /* - * The 2-argument rebind doesn't take an instance - * variable. Our rebind function needs the instance + * The 2-argument rebind doesn't take an instance variable. Our rebind function needs the instance * variable for the username, password, etc. */ if (inst->rebind == 1) { - radlog(L_ERR, "rlm_ldap (%s): Cannot use 'rebind' directive " - "as this version of libldap does not support the API " - "that we need", inst->xlat-name); + LDAP_ERR("Cannot use 'rebind' directive as this version of libldap does not support the API " + "that we need"); + goto error; } #endif @@ -1575,14 +640,14 @@ static int ldap_instantiate(CONF_SECTION * conf, void **instance) /* * Build the attribute map */ - if (ldap_map_verify(inst, &(inst->user_map)) < 0) { + if (rlm_ldap_map_verify(inst, &(inst->user_map)) < 0) { goto error; } /* * Group comparison checks. */ - paircompare_register(PW_LDAP_GROUP, PW_USER_NAME, ldap_groupcmp, inst); + paircompare_register(PW_LDAP_GROUP, PW_USER_NAME, rlm_ldap_groupcmp, inst); if (cf_section_name2(conf)) { const DICT_ATTR *da; ATTR_FLAGS flags; @@ -1595,13 +660,12 @@ static int ldap_instantiate(CONF_SECTION * conf, void **instance) dict_addattr(buffer, -1, 0, PW_TYPE_STRING, flags); da = dict_attrbyname(buffer); if (!da) { - radlog(L_ERR, "rlm_ldap (%s): Failed creating " - "attribute %s", inst->xlat_name, buffer); + LDAP_ERR("Failed creating attribute %s", buffer); + goto error; } - paircompare_register(da->attr, PW_USER_NAME, ldap_groupcmp, - inst); + paircompare_register(da->attr, PW_USER_NAME, rlm_ldap_groupcmp, inst); } xlat_register(inst->xlat_name, ldap_xlat, inst); @@ -1609,10 +673,7 @@ static int ldap_instantiate(CONF_SECTION * conf, void **instance) /* * Initialize the socket pool. */ - inst->pool = fr_connection_pool_init(inst->cs, inst, - ldap_conn_create, - NULL, - ldap_conn_delete); + inst->pool = fr_connection_pool_init(inst->cs, inst, rlm_ldap_conn_create, NULL, rlm_ldap_conn_delete); if (!inst->pool) { ldap_detach(inst); return -1; @@ -1625,304 +686,97 @@ error: return -1; } -static int check_access(ldap_instance *inst, REQUEST* request, LDAP_CONN *conn, - LDAPMessage *entry) +/** Check the user's password against ldap database + * + */ +static rlm_rcode_t ldap_authenticate(void *instance, REQUEST *request) { - int rcode = -1; - char **vals = NULL; - - vals = ldap_get_values(conn->handle, entry, inst->access_attr); - if (vals) { - if (inst->positive_access_attr) { - if (strncmp(vals[0], "FALSE", 5) == 0) { - RDEBUG("Dialup access disabled"); - - } else { - rcode = 0; - } - - } else { - RDEBUG("\"%s\" attribute exists - access denied by" - " default", inst->access_attr); - } - - ldap_value_free(vals); - - } else if (inst->positive_access_attr) { - RDEBUG("No %s attribute - access denied by default", - inst->access_attr); - - } else { - rcode = 0; - } - - return rcode; -} - + rlm_rcode_t rcode; + const char *user_dn; + ldap_instance_t *inst = instance; + ldap_handle_t *conn; -static VALUE_PAIR *ldap_getvalue(REQUEST *request, const value_pair_map_t *map, - void *ctx) -{ - rlm_ldap_result_t *self = ctx; - VALUE_PAIR *head, **tail, *vp; - int i; - - request = request; - - head = NULL; - tail = &head; - /* - * Iterate over all the retrieved values, - * don't try and be clever about changing operators - * just use whatever was set in the attribute map. + * Ensure that we're being passed a plain-text password, and not + * anything else. */ - for (i = 0; i < self->count; i++) { - vp = pairalloc(NULL, map->dst->da); - rad_assert(vp); - - pairparsevalue(vp, self->values[i]); - - *tail = vp; - tail = &(vp->next); - } - - return head; -} - - -static void xlat_attrsfree(const xlat_attrs_t *expanded) -{ - const value_pair_map_t *map; - unsigned int total = 0; - - const char *name; - - for (map = expanded->maps; map != NULL; map = map->next) - { - name = expanded->attrs[total++]; - if (!name) return; - - switch (map->src->type) - { - case VPT_TYPE_XLAT: - case VPT_TYPE_ATTR: - rad_cfree(name); - break; - default: - break; - } - } -} - - -static int xlat_attrs(REQUEST *request, const value_pair_map_t *maps, - xlat_attrs_t *expanded) -{ - const value_pair_map_t *map; - unsigned int total = 0; - - size_t len; - char *buffer; - VALUE_PAIR *found, **from = NULL; - REQUEST *context; - - for (map = maps; map != NULL; map = map->next) - { - switch (map->src->type) - { - case VPT_TYPE_XLAT: - buffer = rad_malloc(MAX_ATTR_STR_LEN); - len = radius_xlat(buffer, MAX_ATTR_STR_LEN, - map->src->name, request, NULL, NULL); - - if (len <= 0) { - RDEBUG("Expansion of LDAP attribute " - "\"%s\" failed", map->src->name); - - goto error; - } - - expanded->attrs[total++] = buffer; - break; + if (!request->username) { + RDEBUGE("Attribute \"User-Name\" is required for authentication"); - case VPT_TYPE_ATTR: - context = request; - - if (radius_request(&context, map->src->request) == 0) { - from = radius_list(context, map->src->list); - } - if (!from) continue; - - found = pairfind(*from, map->src->da->attr, - map->src->da->vendor, TAG_ANY); - if (!found) continue; - - buffer = rad_malloc(MAX_ATTR_STR_LEN); - strlcpy(buffer, found->vp_strvalue, MAX_ATTR_STR_LEN); - - expanded->attrs[total++] = buffer; - break; - - case VPT_TYPE_LITERAL: - expanded->attrs[total++] = map->src->name; - break; - default: - rad_assert(0); - error: - expanded->attrs[total] = NULL; - - xlat_attrsfree(expanded); - - return -1; - } - + return RLM_MODULE_INVALID; } - - expanded->attrs[total] = NULL; - expanded->maps = maps; - - return 0; -} - -/** Convert attribute map into valuepairs - * - * Use the attribute map built earlier to convert LDAP values into valuepairs - * and insert them into whichever list they need to go into. - * - * This is *NOT* atomic, but there's no condition in which we should error - * out... - */ -static void do_attrmap(UNUSED ldap_instance *inst, REQUEST *request, - LDAP *handle, const xlat_attrs_t *expanded, - LDAPMessage *entry) -{ - const value_pair_map_t *map; - unsigned int total = 0; - - rlm_ldap_result_t result; - const char *name; - - for (map = expanded->maps; map != NULL; map = map->next) - { - name = expanded->attrs[total++]; + if (!request->password || + (request->password->da->attr != PW_USER_PASSWORD)) { + RDEBUGW(" You have set \"Auth-Type := LDAP\" somewhere."); + RDEBUGW(" *********************************************"); + RDEBUGW(" * THAT CONFIGURATION IS WRONG. DELETE IT. "); + RDEBUGW(" * YOU ARE PREVENTING THE SERVER FROM WORKING."); + RDEBUGW(" *********************************************"); - result.values = ldap_get_values(handle, entry, name); - if (!result.values) { - RDEBUG2("Attribute \"%s\" not found in LDAP object", - name); - - goto next; - } - - /* - * Find out how many values there are for the - * attribute and extract all of them. - */ - result.count = ldap_count_values(result.values); - - /* - * If something bad happened, just skip, this is probably - * a case of the dst being incorrect for the current - * request context - */ - if (radius_map2request(request, map, name, ldap_getvalue, - &result) < 0) { - goto next; - } - - next: + RDEBUGE("Attribute \"User-Password\" is required for authentication."); - ldap_value_free(result.values); + return RLM_MODULE_INVALID; } -} - - -static void do_check_reply(ldap_instance *inst, REQUEST *request) -{ - /* - * More warning messages for people who can't be bothered - * to read the documentation. - */ - if (inst->expect_password && (debug_flag > 1)) { - if (!pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY) && - !pairfind(request->config_items, PW_NT_PASSWORD, 0, TAG_ANY) && - !pairfind(request->config_items, PW_USER_PASSWORD, 0, TAG_ANY) && - !pairfind(request->config_items, PW_PASSWORD_WITH_HEADER, 0, TAG_ANY) && - !pairfind(request->config_items, PW_CRYPT_PASSWORD, 0, TAG_ANY)) { - RDEBUGW("No \"known good\" password " - "was found in LDAP. Are you sure that " - "the user is configured correctly?"); - } - } -} - -static void apply_profile(ldap_instance *inst, REQUEST *request, - LDAP_CONN **pconn, const char *profile, - const xlat_attrs_t *expanded) -{ - int rcode; - LDAPMessage *result, *entry; - int ldap_errno; - LDAP *handle = (*pconn)->handle; - char filter[MAX_FILTER_STR_LEN]; + if (request->password->length == 0) { + RDEBUGE("Empty password supplied"); + + return RLM_MODULE_INVALID; + } - if (!profile || !*profile) return; + RDEBUG("Login attempt by \"%s\" with password \"%s\"", request->username->vp_strvalue, + request->password->vp_strvalue); - strlcpy(filter, inst->base_filter, sizeof(filter)); + conn = rlm_ldap_get_socket(inst, request); + if (!conn) return RLM_MODULE_FAIL; - rcode = perform_search(inst, request, pconn, profile, LDAP_SCOPE_BASE, - filter, expanded->attrs, &result); + /* + * Get the DN by doing a search. + */ + user_dn = rlm_ldap_find_user(inst, request, &conn, NULL, NULL, &rcode); + if (!user_dn) { + rlm_ldap_release_socket(inst, conn); - if (rcode < 0) { - if (rcode == -2) { - RDEBUG("Profile \"%s\" not found", profile); - } - goto free_result; + return rcode; } - entry = ldap_first_entry(handle, result); - if (!entry) { - ldap_get_option(handle, LDAP_OPT_RESULT_CODE, - &ldap_errno); - radlog(L_ERR, "rlm_ldap (%s): Failed retrieving entry: %s", - inst->xlat_name, - ldap_err2string(ldap_errno)); - - goto free_result; + /* + * Bind as the user + */ + conn->rebound = TRUE; + rcode = rlm_ldap_bind(inst, request, &conn, user_dn, request->password->vp_strvalue, TRUE); + if (rcode == RLM_MODULE_OK) { + RDEBUG("Bind as user \"%s\" was successful", user_dn); } - - do_attrmap(inst, request, handle, expanded, entry); -free_result: - ldap_msgfree(result); + rlm_ldap_release_socket(inst, conn); + + return rcode; } - /** Check if user is authorized for remote access * */ static rlm_rcode_t ldap_authorize(void *instance, REQUEST *request) { - int rcode; - int module_rcode = RLM_MODULE_OK; - ldap_instance *inst = instance; + ldap_rcode_t status; + rlm_rcode_t rcode = RLM_MODULE_OK; + ldap_instance_t *inst = instance; char *user_dn = NULL; char **vals; VALUE_PAIR *vp; - LDAP_CONN *conn; + ldap_handle_t *conn; LDAPMessage *result, *entry; int ldap_errno; - char filter[MAX_FILTER_STR_LEN]; - char basedn[MAX_FILTER_STR_LEN]; - xlat_attrs_t expanded; /* faster that mallocing every time */ + char filter[LDAP_MAX_FILTER_STR_LEN]; + char basedn[LDAP_MAX_FILTER_STR_LEN]; + rlm_ldap_map_xlat_t expanded; /* faster that mallocing every time */ if (!request->username) { - RDEBUG2("Attribute \"User-Name\" is required for " - "authorization."); + RDEBUG2("Attribute \"User-Name\" is required for authorization."); + return RLM_MODULE_NOOP; } @@ -1931,73 +785,60 @@ static rlm_rcode_t ldap_authorize(void *instance, REQUEST *request) */ if (request->username->length == 0) { RDEBUG2("Zero length username not permitted"); + return RLM_MODULE_INVALID; } - if (!radius_xlat(filter, sizeof(filter), inst->filter, - request, ldap_escape_func, NULL)) { - radlog(L_ERR, "rlm_ldap (%s): Failed creating filter", - inst->xlat_name); + if (!radius_xlat(filter, sizeof(filter), inst->filter, request, rlm_ldap_escape_func, NULL)) { + RDEBUGE("Failed creating filter"); + return RLM_MODULE_INVALID; } - if (!radius_xlat(basedn, sizeof(basedn), inst->basedn, - request, ldap_escape_func, NULL)) { - radlog(L_ERR, "rlm_ldap (%s): Failed creating basedn", - inst->xlat_name); + if (!radius_xlat(basedn, sizeof(basedn), inst->basedn, request, rlm_ldap_escape_func, NULL)) { + RDEBUGE("Failed creating basedn"); + return RLM_MODULE_INVALID; } - if (xlat_attrs(request, inst->user_map, &expanded) < 0) { + if (rlm_ldap_map_xlat(request, inst->user_map, &expanded) < 0) { return RLM_MODULE_FAIL; } - - conn = ldap_get_socket(inst); + conn = rlm_ldap_get_socket(inst, request); if (!conn) return RLM_MODULE_FAIL; - rcode = perform_search(inst, request, &conn, basedn, - LDAP_SCOPE_SUBTREE, filter, expanded.attrs, - &result); - - if (rcode < 0) { - if (rcode == -2) { + status = rlm_ldap_search(inst, request, &conn, basedn, LDAP_SCOPE_SUBTREE, filter, expanded.attrs, &result); + switch (status) { + case LDAP_PROC_SUCCESS: + break; + case LDAP_PROC_NO_RESULT: + rcode = RLM_MODULE_NOTFOUND; RDEBUGE("User object not found"); - module_rcode = RLM_MODULE_NOTFOUND; - + default: goto free_socket; - } - - goto free_socket; } + + rad_assert(conn); entry = ldap_first_entry(conn->handle, result); if (!entry) { - ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, - &ldap_errno); - radlog(L_ERR, "rlm_ldap (%s): Failed retrieving entry: %s", - inst->xlat_name, - ldap_err2string(ldap_errno)); + ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); + RDEBUGE("Failed retrieving entry: %s", + ldap_err2string(ldap_errno)); goto free_result; } user_dn = ldap_get_dn(conn->handle, entry); if (!user_dn) { - ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, - &ldap_errno); - radlog(L_ERR, "rlm_ldap (%s): ldap_get_dn() failed: %s", - inst->xlat_name, - ldap_err2string(ldap_errno)); + ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); + RDEBUGE("ldap_get_dn() failed: %s", ldap_err2string(ldap_errno)); + goto free_result; } RDEBUG2("User found at DN \"%s\"", user_dn); - /* - * Adding attribute containing the Users' DN. - */ - pairadd(&request->config_items, - pairmake("Ldap-UserDn", user_dn, T_OP_EQ)); #ifdef WITH_EDIR /* @@ -2018,8 +859,7 @@ static rlm_rcode_t ldap_authorize(void *instance, REQUEST *request) bufsize = sizeof(buffer); /* retrive universal password */ - res = nmasldap_get_password(conn->handle, user_dn, - buffer, &bufsize); + res = nmasldap_get_password(conn->handle, user_dn, buffer, &bufsize); if (res != 0) { RDEBUGW("Failed to retrieve eDirectory password"); module_rcode = RLM_MODULE_NOOP; @@ -2028,25 +868,19 @@ static rlm_rcode_t ldap_authorize(void *instance, REQUEST *request) } /* Add Cleartext-Password attribute to the request */ - vp = radius_paircreate(request, &request->config_items, - PW_CLEARTEXT_PASSWORD, 0); + vp = radius_paircreate(request, &request->config_items, PW_CLEARTEXT_PASSWORD, 0); strlcpy(vp->vp_strvalue, buffer, sizeof(vp->vp_strvalue)); vp->length = strlen(vp->vp_strvalue); - RDEBUG2("Added eDirectory password in check items as %s = %s", - vp->da->name, vp->vp_strvalue); + RDEBUG2("Added eDirectory password in check items as %s = %s", vp->da->name, vp->vp_strvalue); if (inst->edir_autz) { - RDEBUG2("Binding as user for eDirectory authorization " - "checks"); + RDEBUG2("Binding as user for eDirectory authorization checks"); /* * Bind as the user */ conn->rebound = TRUE; - module_rcode = ldap_bind_wrapper(request, &conn, - user_dn, - vp->vp_strvalue, - TRUE); + module_rcode = ldap_bind_user(request, &conn, user_dn, vp->vp_strvalue, TRUE); if (module_rcode != RLM_MODULE_OK) { goto free_result; } @@ -2061,9 +895,9 @@ skip_edir: /* * Check for access. */ - if (inst->access_attr) { - if (check_access(inst, request, conn, entry) < 0) { - module_rcode = RLM_MODULE_USERLOCK; + if (inst->userobj_access_attr) { + rcode = rlm_ldap_check_access(inst, request, conn, entry); + if (rcode != RLM_MODULE_OK) { goto free_result; } } @@ -2073,11 +907,11 @@ skip_edir: */ vp = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY); if (vp || inst->default_profile) { - char *profile = inst->default_profile; + const char *profile = inst->default_profile; if (vp) profile = vp->vp_strvalue; - apply_profile(inst, request, &conn, profile, &expanded); + rlm_ldap_apply_profile(inst, request, &conn, profile, &expanded); } /* @@ -2088,10 +922,8 @@ skip_edir: if (vals != NULL) { int i; - for (i = 0; (vals[i] != NULL) && (*vals[i] != '\0'); - i++) { - apply_profile(inst, request, &conn, vals[i], - &expanded); + for (i = 0; (vals[i] != NULL) && (*vals[i] != '\0'); i++) { + rlm_ldap_apply_profile(inst, request, &conn, vals[i], &expanded); } ldap_value_free(vals); @@ -2099,126 +931,48 @@ skip_edir: } if (inst->user_map) { - do_attrmap(inst, request, conn->handle, &expanded, entry); - do_check_reply(inst, request); + rlm_ldap_map_do(inst, request, conn->handle, &expanded, entry); + rlm_ldap_check_reply(inst, request); } free_result: if (user_dn) ldap_memfree(user_dn); - xlat_attrsfree(&expanded); + rlm_ldap_map_xlat_free(&expanded); ldap_msgfree(result); free_socket: - ldap_release_socket(inst, conn); - - return module_rcode; -} - - -/** Check the user's password against ldap database - * - */ -static rlm_rcode_t ldap_authenticate(void *instance, REQUEST *request) -{ - rlm_rcode_t module_rcode; - const char *user_dn; - ldap_instance *inst = instance; - LDAP_CONN *conn; - - /* - * Ensure that we're being passed a plain-text password, and not - * anything else. - */ - - if (!request->username) { - radlog(L_AUTH, "rlm_ldap (%s): Attribute \"User-Name\" is " - "required for authentication", inst->xlat_name); - return RLM_MODULE_INVALID; - } - - if (!request->password) { - radlog(L_AUTH, "rlm_ldap (%s): Attribute \"User-Password\" " - "is required for authentication.", inst->xlat_name); - RDEBUG2(" You have set \"Auth-Type := LDAP\" somewhere."); - RDEBUG2(" *********************************************"); - RDEBUG2(" * THAT CONFIGURATION IS WRONG. DELETE IT. "); - RDEBUG2(" * YOU ARE PREVENTING THE SERVER FROM WORKING."); - RDEBUG2(" *********************************************"); - return RLM_MODULE_INVALID; - } - - if (request->password->da->attr != PW_USER_PASSWORD) { - radlog(L_AUTH, "rlm_ldap (%s): Attribute \"User-Password\" " - "is required for authentication. Cannot use \"%s\".", - inst->xlat_name, request->password->da->name); - return RLM_MODULE_INVALID; - } - - if (request->password->length == 0) { - RDEBUGE("Empty password supplied"); - - return RLM_MODULE_INVALID; - } - - RDEBUG("Login attempt by \"%s\" with password \"%s\"", - request->username->vp_strvalue, request->password->vp_strvalue); - - conn = ldap_get_socket(inst); - if (!conn) return RLM_MODULE_FAIL; + rlm_ldap_release_socket(inst, conn); - /* - * Get the DN by doing a search. - */ - user_dn = get_userdn(&conn, request, &module_rcode); - if (!user_dn) { - ldap_release_socket(inst, conn); - return module_rcode; - } - - /* - * Bind as the user - */ - conn->rebound = TRUE; - module_rcode = ldap_bind_wrapper(request, &conn, user_dn, - request->password->vp_strvalue, - TRUE); - if (module_rcode == RLM_MODULE_OK) { - RDEBUG("Bind as user \"%s\" was successful", user_dn); - } - - ldap_release_socket(inst, conn); - return module_rcode; + return rcode; } /** Modify user's object in LDAP * + * Process a modifcation map to update a user object in the LDAP directory. + * + * @param inst rlm_ldap instance. + * @param request Current request. + * @param section that holds the map to process. + * @return one of the RLM_MODULE_* values. */ -static rlm_rcode_t user_modify(ldap_instance *inst, REQUEST *request, - ldap_acct_section_t *section) +static rlm_rcode_t user_modify(ldap_instance_t *inst, REQUEST *request, ldap_acct_section_t *section) { - rlm_rcode_t module_rcode = RLM_MODULE_OK; - int ldap_errno, rcode, msg_id; - LDAPMessage *result = NULL; + rlm_rcode_t rcode = RLM_MODULE_OK; - LDAP_CONN *conn = NULL; + ldap_handle_t *conn = NULL; - LDAPMod *mod_p[MAX_ATTRMAP + 1], mod_s[MAX_ATTRMAP]; + LDAPMod *mod_p[LDAP_MAX_ATTRMAP + 1], mod_s[LDAP_MAX_ATTRMAP]; LDAPMod **modify = mod_p; - char *passed[MAX_ATTRMAP * 2]; + char *passed[LDAP_MAX_ATTRMAP * 2]; int i, total = 0, last_pass = 0; - char *expanded[MAX_ATTRMAP]; + char *expanded[LDAP_MAX_ATTRMAP]; int last_exp = 0; - struct timeval tv; - const char *attr; const char *value; - const char *user_dn; - - const char *error = NULL; - + const char *dn; /* * Build our set of modifications using the update sections in * the config. @@ -2240,8 +994,7 @@ static rlm_rcode_t user_modify(ldap_instance *inst, REQUEST *request, *p++ = '.'; } - if (!radius_xlat(p, (sizeof(path) - (p - path)) - 1, - section->reference, request, NULL, NULL)) { + if (!radius_xlat(p, (sizeof(path) - (p - path)) - 1, section->reference, request, NULL, NULL)) { goto error; } @@ -2251,17 +1004,14 @@ static rlm_rcode_t user_modify(ldap_instance *inst, REQUEST *request, } if (!cf_item_is_section(ci)){ - radlog(L_ERR, "rlm_ldap (%s): Reference must resolve to a " - "section", inst->xlat_name); + RDEBUGE("Reference must resolve to a section"); goto error; } cs = cf_section_sub_find(cf_itemtosection(ci), "update"); if (!cs) { - radlog(L_ERR, "rlm_ldap (%s): Section must contain 'update' " - "subsection", - inst->xlat_name); + RDEBUGE("Section must contain 'update' subsection"); goto error; } @@ -2269,22 +1019,17 @@ static rlm_rcode_t user_modify(ldap_instance *inst, REQUEST *request, /* * Iterate over all the pairs, building our mods array */ - for (ci = cf_item_find_next(cs, NULL); - ci != NULL; - ci = cf_item_find_next(cs, ci)) { + for (ci = cf_item_find_next(cs, NULL); ci != NULL; ci = cf_item_find_next(cs, ci)) { int do_xlat = FALSE; - if (total == MAX_ATTRMAP) { - radlog(L_ERR, "rlm_ldap (%s): Modify map size exceeded", - inst->xlat_name); + if (total == LDAP_MAX_ATTRMAP) { + RDEBUGE("Modify map size exceeded"); goto error; } if (!cf_item_is_pair(ci)) { - radlog(L_ERR, "rlm_ldap (%s): Entry is not in " - "\"ldap-attribute = value\" format", - inst->xlat_name); + RDEBUGE("Entry is not in \"ldap-attribute = value\" format"); goto error; } @@ -2297,9 +1042,8 @@ static rlm_rcode_t user_modify(ldap_instance *inst, REQUEST *request, attr = cf_pair_attr(cp); op = cf_pair_operator(cp); - if ((value == NULL) || (*value == '\0')) { - RDEBUG("empty value string, " - "skipping attribute \"%s\"", attr); + if (!value || (*value == '\0')) { + RDEBUG("Empty value string, skipping attribute \"%s\"", attr); continue; } @@ -2323,8 +1067,7 @@ static rlm_rcode_t user_modify(ldap_instance *inst, REQUEST *request, } else if (do_xlat) { p = rad_malloc(1024); if (radius_xlat(p, 1024, value, request, NULL, NULL) <= 0) { - RDEBUG("xlat failed or empty value string, " - "skipping attribute \"%s\"", attr); + RDEBUG("xlat failed or empty value string, skipping attribute \"%s\"", attr); free(p); @@ -2337,8 +1080,7 @@ static rlm_rcode_t user_modify(ldap_instance *inst, REQUEST *request, * Static strings */ } else { - memcpy(&(passed[last_pass]), &value, - sizeof(passed[last_pass])); + memcpy(&(passed[last_pass]), &value, sizeof(passed[last_pass])); } passed[last_pass + 1] = NULL; @@ -2372,10 +1114,8 @@ static rlm_rcode_t user_modify(ldap_instance *inst, REQUEST *request, break; #endif default: - radlog(L_ERR, "rlm_ldap (%s): Operator '%s' " - "is not supported for LDAP modify " - "operations", inst->xlat_name, - fr_int2str(fr_tokens, op, "¿unknown?")); + RDEBUGE("Operator '%s' is not supported for LDAP modify operations", + fr_int2str(fr_tokens, op, "¿unknown?")); goto error; } @@ -2384,112 +1124,46 @@ static rlm_rcode_t user_modify(ldap_instance *inst, REQUEST *request, * Now we know the value is ok, copy the pointers into * the ldapmod struct. */ - memcpy(&(mod_s[total].mod_type), &(attr), - sizeof(mod_s[total].mod_type)); + memcpy(&(mod_s[total].mod_type), &(attr), sizeof(mod_s[total].mod_type)); mod_p[total] = &(mod_s[total]); total++; } if (total == 0) { - module_rcode = RLM_MODULE_NOOP; + rcode = RLM_MODULE_NOOP; goto release; } mod_p[total] = NULL; - conn = ldap_get_socket(inst); + conn = rlm_ldap_get_socket(inst, request); if (!conn) return RLM_MODULE_FAIL; - - /* - * Perform all modifications as the default admin user. - */ - if (conn->rebound) { - ldap_errno = ldap_bind_wrapper(request, &conn, inst->login, - inst->password, TRUE); - if (ldap_errno != RLM_MODULE_OK) { - goto error; - } - rad_assert(conn != NULL); - conn->rebound = FALSE; - } - user_dn = get_userdn(&conn, request, &module_rcode); - if (!user_dn) { - module_rcode = RLM_MODULE_NOTFOUND; - goto release; - } - - RDEBUG2("Modifying user object with DN \"%s\"", user_dn); - retry: - ldap_errno = ldap_modify_ext(conn->handle, user_dn, modify, NULL, NULL, - &msg_id); - if (ldap_errno != LDAP_SUCCESS) { - switch (process_ldap_errno(inst, conn, &error)) - { - case LDAP_PROC_SUCCESS: - break; - case LDAP_PROC_RETRY: - radlog(L_ERR, "rlm_ldap (%s): Failed " - "modifying object, reconnecting: %s", - inst->xlat_name, error); - goto retry; - default: - radlog(L_ERR, "rlm_ldap (%s): Failed " - "modifying object: %s", - inst->xlat_name, error); - goto error; - } + dn = rlm_ldap_find_user(inst, request, &conn, NULL, NULL, &rcode); + if (!dn || (rcode = RLM_MODULE_OK)) { + goto error; } - - DEBUG3("rlm_ldap (%s): Waiting for modify result...", inst->xlat_name); - - tv.tv_sec = inst->timeout; - tv.tv_usec = 0; - result: - rcode = ldap_result(conn->handle, msg_id, 1, &tv, &result); - ldap_msgfree(result); - if (rcode <= 0) { - switch (process_ldap_errno(inst, conn, &error)) - { - case LDAP_PROC_SUCCESS: - break; - case LDAP_PROC_RETRY: - radlog(L_ERR, "rlm_ldap (%s): Failed " - "modifying object, reconnecting: %s", - inst->xlat_name, error); - goto result; - default: - error: - radlog(L_ERR, "rlm_ldap (%s): Failed " - "modifying object: %s", - inst->xlat_name, error); - module_rcode = RLM_MODULE_FAIL; - goto release; - } - } - - RDEBUG2("Modification successful!"); + rcode = rlm_ldap_modify(inst, request, &conn, dn, modify); release: + error: /* * Free up any buffers we allocated for xlat expansion */ for (i = 0; i < last_exp; i++) { free(expanded[i]); } - - ldap_release_socket(inst, conn); + rlm_ldap_release_socket(inst, conn); - return module_rcode; + return rcode; } - static rlm_rcode_t ldap_accounting(void *instance, REQUEST * request) { - ldap_instance *inst = instance; + ldap_instance_t *inst = instance; if (inst->accounting) { return user_modify(inst, request, inst->accounting); @@ -2504,7 +1178,7 @@ static rlm_rcode_t ldap_accounting(void *instance, REQUEST * request) { */ static rlm_rcode_t ldap_postauth(void *instance, REQUEST * request) { - ldap_instance *inst = instance; + ldap_instance_t *inst = instance; if (inst->postauth) { return user_modify(inst, request, inst->postauth); diff --git a/src/modules/rlm_pap/rlm_pap.c b/src/modules/rlm_pap/rlm_pap.c index f708586faf3..d3e5909a901 100644 --- a/src/modules/rlm_pap/rlm_pap.c +++ b/src/modules/rlm_pap/rlm_pap.c @@ -211,7 +211,7 @@ static rlm_rcode_t pap_authorize(void *instance, REQUEST *request) * that instead of this one. */ if (pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) { - RDEBUG("Config already contains \"known good\" password. Ignoring Password-With-Header"); + RDEBUG("Config already contains \"reference\" password. Ignoring Password-With-Header"); break; } @@ -337,8 +337,8 @@ static rlm_rcode_t pap_authorize(void *instance, REQUEST *request) return RLM_MODULE_NOOP; } - RDEBUGW("No \"known good\" password found for the user. Not setting Auth-Type."); - RDEBUGW("Authentication will fail unless a \"known good\" password is available."); + RDEBUGW("No \"reference\" password found for the user. Not setting Auth-Type."); + RDEBUGW("Authentication will fail unless a \"reference\" password is available."); return RLM_MODULE_NOOP; }