Index: modules/ldap/util_ldap.c =================================================================== --- modules/ldap/util_ldap.c (revision 489745) +++ modules/ldap/util_ldap.c (working copy) @@ -703,6 +703,264 @@ } +static int uldap_cache_comparedynamicgroup(request_rec *r, + util_ldap_connection_t *ldc, + const char *url, const char *dn, + const char *base, int scope, + const char *user, const char *userdn) +{ + + int result = 0; + int failures = 0; + int i; + LDAPMessage *groupResult; + char *groupAttr[] = { "objectClass", "memberURL", NULL}; + char *uidAttr[] = { "uid", NULL}; + char **attr, **tmpAttr; + char memberURL[255]; + apr_ldap_url_desc_t *groupUrl, *origUrl; + apr_ldap_err_t *lgroupResult; + + +start_over: + if (failures++ > 10) { + return result; + } + + if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) { + return result; + } + + /* first thing we do is find if the specified group has any memberURL entries */ + if ((result = ldap_search_s(ldc->ldap, base, scope, dn, + groupAttr, 0, &groupResult)) + == LDAP_SERVER_DOWN) { + + ldc->reason = "ldap_search_s() failed with server down"; + uldap_connection_unbind(ldc); + goto start_over; + } + + ldc->reason = "LDAP search for dynamic group members complete"; + + if ((result = ldap_count_entries(ldc->ldap, groupResult)) != 1) { + ldc->reason = "LDAP search for dynamic group didn't have 1 result"; + ldap_msgfree(groupResult); + return LDAP_COMPARE_FALSE; + } + + if ((groupResult = ldap_first_entry(ldc->ldap, groupResult)) == NULL) { + ldc->reason = "ldap_first_entry return NULL"; + return LDAP_COMPARE_FALSE; + } + + attr = ldap_get_values(ldc->ldap, groupResult, "objectClass"); + + if (attr = NULL) { + ldc->reason = "ldap_get_values() for objectClass returned NULL"; + return LDAP_COMPARE_FALSE; + } + + if ((attr = ldap_get_values(ldc->ldap, groupResult, "memberURL")) == NULL) { + ldc->reason = "ldap_get_values() for \"memberURL\" return NULL"; + return LDAP_COMPARE_FALSE; + } + + tmpAttr = attr; + i = 0; + + while (*tmpAttr != NULL) { + bzero(memberURL, sizeof(memberURL)); + strcpy(memberURL, *tmpAttr++); + + /* now, memberURL should be an LDAP URL pointing to a group */ + if ((result = apr_ldap_url_parse(r->pool, + (char *)memberURL, + &groupUrl, + &lgroupResult)) + != APR_SUCCESS) + { + ldc->reason = "could not parse returned URL"; + continue; + } + else { + /* we have a good LDAP url, now search to see if the user is there */ + + if ((result = ldap_search_s(ldc->ldap, userdn, LDAP_SCOPE_SUBTREE, + groupUrl->lud_filter, uidAttr, + 0, &groupResult)) + != LDAP_SUCCESS) + { + continue; + } + else { + if (ldap_count_entries(ldc->ldap, groupResult) >= 1) { + return LDAP_COMPARE_TRUE; + } + } + + } + + + + + } + + return LDAP_COMPARE_FALSE; +} + +char ** uldap_cache_getattributevalues(request_rec *r, util_ldap_connection_t *ldc, + const char *url, const char *dn, + const char *attrib) +{ + char **arr = NULL; + char **attr; + + int failures = 0; + int result = 0; + int count; + char *filter = NULL; + int numvals = 0; + + LDAPMessage *res, *entry; + + util_url_node_t *curl; + util_url_node_t curnode; + util_search_node_t *search_nodep; + util_search_node_t the_search_node; + apr_time_t curtime; + + util_ldap_state_t *st = + (util_ldap_state_t *)ap_get_module_config(r->server->module_config, + &ldap_module); + + LDAP_CACHE_LOCK(); + curnode.url = url; + curl = (util_url_node_t *)util_ald_cache_fetch(st->util_ldap_cache, + &curnode); + + char * key = apr_pstrcat(r->pool, dn, "::", attrib, NULL); + + if (curl == NULL) { + curl = util_ald_create_caches(st, url); + } + LDAP_CACHE_UNLOCK(); + + if (curl) { + LDAP_CACHE_LOCK(); + the_search_node.username = key; + search_nodep = util_ald_cache_fetch(curl->search_cache, + &the_search_node); + + if (search_nodep != NULL) { + /* found attributes in search cache */ + + curtime = apr_time_now(); + + if ((curtime - search_nodep->lastbind) > st->search_cache_ttl) { + /* ...but entry is too old */ + util_ald_cache_remove(curl->search_cache, search_nodep); + } + else { + /* entry isn't too old, so return the results */ + arr = (char **)(search_nodep->vals); + LDAP_CACHE_UNLOCK(); + return arr; + } + } + + LDAP_CACHE_UNLOCK(); + + } + + /* didn't get hit from cache */ +start_over: + if (failures++ > 10) { + return arr; + } + if (LDAP_SUCCESS != (result = uldap_connection_open(r, ldc))) { + return arr; + } + + /* try do the search */ + filter = apr_pstrcat(st->pool, "objectClass", attrib, NULL); + if ((result = ldap_search_ext_s(ldc->ldap, + (char *)dn, LDAP_SCOPE_SUBTREE, + filter, (char *)(attrib), 0, + NULL, NULL, NULL, -1, &res)) + == LDAP_SERVER_DOWN) + { + ldc->reason = "ldap_search_ext_s() for attribute failed with server down"; + uldap_connection_unbind(ldc); + goto start_over; + } + + /* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */ + if (result != LDAP_SUCCESS) { + ldc->reason = "ldap_search_ext_s() for attribute failed"; + return arr; + } + + /* + * We should have found exactly one entry; to find a different + * number is an error. + */ + count = ldap_count_entries(ldc->ldap, res); + if (count != 1) + { + if (count == 0 ) + ldc->reason = "Attribute not found"; + else + ldc->reason = "Attribute search found multiple records. This shouldn't happen"; + ldap_msgfree(res); + return arr; + } + else { + entry = ldap_first_entry(ldc->ldap, res); + + attr = ldap_get_values(ldc->ldap, entry, "objectClass"); + + if (attr == NULL) { + ldc->reason = "ldap_get_values() for objectClass returned NULL"; + return arr; + } + + if ((attr = ldap_get_values(ldc->ldap, entry, attrib)) == NULL) { + ldc->reason = "ldap_get_values() for attribute return NULL"; + return arr; + } + + /* attr has the values */ + if (attr) { + int k = 0; + int i = 0; + + while (attr[k++]); + arr = apr_pcalloc(r->pool, sizeof(char *) * (k+1)); + numvals = k; + + while (attr[i]) { + char **values; + int j = 0; + char *str = NULL; + + values = ldap_get_values(ldc->ldap, entry, attr[i]); + while (values && values[j]) { + str = str ? apr_pstrcat(r->pool, str, "; ", values[j], NULL) + : apr_pstrdup(r->pool, values[j]); + j++; + } + ldap_value_free(values); + arr[i] = str; + i++; + } + } + + } + + return arr; +} + /* * Does an generic ldap_compare operation. It accepts a cache that it will use * to lookup the compare in the cache. We cache two kinds of compares @@ -2099,6 +2357,8 @@ APR_REGISTER_OPTIONAL_FN(uldap_connection_find); APR_REGISTER_OPTIONAL_FN(uldap_cache_comparedn); APR_REGISTER_OPTIONAL_FN(uldap_cache_compare); + APR_REGISTER_OPTIONAL_FN(uldap_cache_comparedynamicgroup); + APR_REGISTER_OPTIONAL_FN(uldap_cache_getattributevalues); APR_REGISTER_OPTIONAL_FN(uldap_cache_checkuserid); APR_REGISTER_OPTIONAL_FN(uldap_cache_getuserdn); APR_REGISTER_OPTIONAL_FN(uldap_ssl_supported); Index: modules/aaa/mod_authnz_ldap.c =================================================================== --- modules/aaa/mod_authnz_ldap.c (revision 489745) +++ modules/aaa/mod_authnz_ldap.c (working copy) @@ -74,6 +74,9 @@ it's the exact string passed by the HTTP client */ int secure; /* True if SSL connections are requested */ + + int dynamic_group_lookup; /* True if dynamic group lookups desired */ + apr_array_header_t *dynamicgroupattr; /* List of dynamic group attributes */ } authn_ldap_config_t; typedef struct { @@ -94,6 +97,8 @@ static APR_OPTIONAL_FN_TYPE(uldap_connection_find) *util_ldap_connection_find; static APR_OPTIONAL_FN_TYPE(uldap_cache_comparedn) *util_ldap_cache_comparedn; static APR_OPTIONAL_FN_TYPE(uldap_cache_compare) *util_ldap_cache_compare; +static APR_OPTIONAL_FN_TYPE(uldap_cache_comparedynamicgroup) *util_ldap_cache_comparedynamicgroup; +static APR_OPTIONAL_FN_TYPE(uldap_cache_getattributevalues) *util_ldap_cache_getattributevalues; static APR_OPTIONAL_FN_TYPE(uldap_cache_checkuserid) *util_ldap_cache_checkuserid; static APR_OPTIONAL_FN_TYPE(uldap_cache_getuserdn) *util_ldap_cache_getuserdn; static APR_OPTIONAL_FN_TYPE(uldap_ssl_supported) *util_ldap_ssl_supported; @@ -287,6 +292,8 @@ */ sec->groupattr = apr_array_make(p, GROUPATTR_MAX_ELTS, sizeof(struct mod_auth_ldap_groupattr_entry_t)); + sec->dynamicgroupattr = apr_array_make(p, GROUPATTR_MAX_ELTS, + sizeof(struct mod_auth_ldap_groupattr_entry_t)); sec->have_ldap_url = 0; sec->url = ""; @@ -306,6 +313,9 @@ sec->user_is_dn = 0; sec->remote_user_attribute = NULL; sec->compare_dn_on_server = 0; + + sec->dynamic_group_lookup = 0; /* dynamic group lookup disabled by default */ + return sec; } @@ -514,8 +524,8 @@ int method_restricted = 0; char filtbuf[FILTER_LENGTH]; - const char *dn = NULL; - const char **vals = NULL; + char *dn = NULL; + char **vals = NULL; /* if (!sec->enabled) { @@ -558,6 +568,22 @@ apr_thread_mutex_unlock(sec->lock); #endif } + + /* + * If there are no elements in the dynamic group attribute array, populate default + * of "memberURL". This is the same code as above block but for dynamicgroupattr array + */ + if (sec->dynamicgroupattr->nelts == 0) { + struct mod_auth_ldap_groupattr_entry_t *dyngrp; +#if APR_HAS_THREADS + apr_thread_mutex_lock(sec->lock); +#endif + dyngrp = apr_array_push(sec->dynamicgroupattr); + dyngrp->name = "memberURL"; +#if APR_HAS_THREADS + apr_thread_mutex_unlock(sec->lock); +#endif + } if (!reqs_arr) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, @@ -738,6 +764,78 @@ } } } + + /* Regular group membership has failed at this point. Do dynamic group checking if it is enabled */ + if (sec->dynamic_group_lookup) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[%" APR_PID_T_FMT "] auth_ldap_authorise: require group: " + "testing for dynamic group: %s (%s)", getpid(), + sec->group_attrib_is_dn ? req->dn : req->user, t); + + for (i = 0; i < sec->dynamicgroupattr->nelts; i++) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[%" APR_PID_T_FMT "] auth_ldap_authorise: require group: " + "dynamic group attribute %s: %s (%s)", getpid(), + ent[i].name, sec->group_attrib_is_dn ? req->dn : req->user, t); + + /* first we get all the attribute values for the current dynamic group attribute */ + vals = util_ldap_cache_getattributevalues(r, ldc, sec->url, req->dn, ent[i].name); + + int key = 0; + + /* loop through all found attribute values and do a search */ + while (vals[key]) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[%" APR_PID_T_FMT "] auth_ldap_authorise: require group: " + , getpid()); + + /* build search filter */ + authn_ldap_build_filter(filtbuf, r, req->user, vals[key], sec); + result = util_ldap_cache_getuserdn(r, ldc, sec->url, sec->basedn, + sec->scope, sec->attributes, filtbuf, &dn, &vals); + + if (result == LDAP_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[%" APR_PID_T_FMT "] auth_ldap_authorise: checking dn match %s", + getpid(), dn); + result = util_ldap_cache_comparedn(r, ldc, sec->url, req->dn, dn, + sec->compare_dn_on_server); + + } + + switch (result) { + case LDAP_COMPARE_TRUE: { + + } + case LDAP_FILTER_ERROR: { + + } + default: { + + } + } + + key++; + } + + switch (result) { + case LDAP_COMPARE_TRUE: { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[%" APR_PID_T_FMT "] auth_ldap_authorise: require dynamic group: " + "authorisation successful [%s][%s]", + getpid(), ldc->reason, ldap_err2string(result)); + return OK; + } + default : { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[%" APR_PID_T_FMT "] auth_ldap authorise: require dynamic group: " + "authorisation failed [%s][%s]", + getpid(), ldc->reason, ldap_err2string(result)); + + } + } + } + } } else if (strcmp(w, "ldap-attribute") == 0) { if (req->dn == NULL || strlen(req->dn) == 0) { @@ -1015,6 +1113,21 @@ return NULL; } +static const char *mod_auth_ldap_add_dynamic_group_attribute(cmd_parms *cmd, void *config, const char *arg) +{ + struct mod_auth_ldap_groupattr_entry_t *new; + + authn_ldap_config_t *sec = config; + + if (sec->dynamicgroupattr->nelts > GROUPATTR_MAX_ELTS) + return "Too many AuthLDAPDynamicGroupAttribute directives"; + + new = apr_array_push(sec->dynamicgroupattr); + new->name = apr_pstrdup(cmd->pool, arg); + + return NULL; +} + static const char *set_charset_config(cmd_parms *cmd, void *config, const char *arg) { ap_set_module_config(cmd->server->module_config, &authnz_ldap_module, @@ -1106,6 +1219,15 @@ "Character set conversion configuration file. If omitted, character set" "conversion is disabled."), + AP_INIT_FLAG("AuthLDAPDynamicGroupLookup", ap_set_flag_slot, + (void *)APR_OFFSETOF(authn_ldap_config_t, dynamic_group_lookup), OR_AUTHCFG, + "If set to 'on', auth_ldap will look for dynamic group URI in a group DN " + "and attempt to see if a user is part of a group defined by that URI " + "Defaults to 'off'."), + + AP_INIT_TAKE1("AuthLDAPDynamicGroupAttribute", mod_auth_ldap_add_dynamic_group_attribute, NULL, OR_AUTHCFG, + "A list of attributes containing dynamic group URIs. Defaults to \"memberURL\"."), + {NULL} }; @@ -1203,6 +1325,8 @@ util_ldap_connection_find = APR_RETRIEVE_OPTIONAL_FN(uldap_connection_find); util_ldap_cache_comparedn = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_comparedn); util_ldap_cache_compare = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_compare); + util_ldap_cache_comparedynamicgroup = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_comparedynamicgroup); + util_ldap_cache_getattributevalues = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_getattributevalues); util_ldap_cache_checkuserid = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_checkuserid); util_ldap_cache_getuserdn = APR_RETRIEVE_OPTIONAL_FN(uldap_cache_getuserdn); util_ldap_ssl_supported = APR_RETRIEVE_OPTIONAL_FN(uldap_ssl_supported); Index: modules/aaa/NWGNUauthnzldap =================================================================== --- modules/aaa/NWGNUauthnzldap (revision 489745) +++ modules/aaa/NWGNUauthnzldap (working copy) @@ -217,6 +217,8 @@ util_ldap_cache_getuserdn \ util_ldap_cache_compare \ util_ldap_cache_comparedn \ + util_ldap_cache_comparedynamicgroup \ + util_ldap_cache_getattributevalues \ @$(APR)/aprlib.imp \ @$(NWOS)/httpd.imp \ @libc.imp \ Index: include/util_ldap.h =================================================================== --- include/util_ldap.h (revision 489745) +++ include/util_ldap.h (working copy) @@ -140,6 +140,9 @@ } util_ldap_state_t; +typedef struct util_ldap_attrvalue_entry_t { + char *value; +} util_ldap_attrvalue_entry_t; /** * Open a connection to an LDAP server @@ -248,6 +251,34 @@ const char *url, const char *dn, const char *attrib, const char *value)); /** + * Checks to see if a DN is part of a dynamic group + * @param r The request record + * @param ldc The LDAP connection being used + * @param url The URL of the LDAP connection - used for deciding which cache to use + * @param dn The DN of the object containing the dynamic group attributes + * @param groupattrib The attribute containing the dynamic group URI's + * @param userattrib The user attribute + * @param base The search base of the connection + * @param scope The search scope of the connection + * @param user The user we are comparing + */ +APR_DECLARE_OPTIONAL_FN(int,uldap_cache_comparedynamicgroup,(request_rec *r, util_ldap_connection_t *ldc, + const char *url, const char *dn, + const char *base, int scope, + const char *user, const char *userdn)); + +/** + * Returns an array of values for an attribute in a DN + * @param r The request record + * @param ldc The LDAP connection being used + * @param url The URL of the LDAP connection - used for deciding which cache to use + * @param dn The DN of the object containing the attribute + * @param attrib The attrib for which to return values + */ +APR_DECLARE_OPTIONAL_FN(char **, uldap_cache_getattributevalues,(request_rec *r, util_ldap_connection_t *ldc, + const char *url, const char *dn, const char *attrib)); + +/** * Checks a username/password combination by binding to the LDAP server * @param r The request record * @param ldc The LDAP connection being used.