The following thread is a note to Dave Carrigan where I proposed the
attached patch to his auth_ldap module for nested group association,
like can be used in MS Active Directory.
I have not compared his code to what exists in Apache 2.x, but I understand
its similar and from the same original source tree.
I thought I would put this code out there, and if there is any interest
in using it, or using its ideas to incorporating into the Apache baseline,
please feel free.
Again, this is a patch against the auth_ldap 1.6.0 module, not the current
apache code.
I'm not a subscriber to the dev mailing list, so questions will need to
addressed directly to me.
Brian Foddy
Northwest Airlines
-------- Original Message --------
Subject: Re: Nested Groups
Date: Mon, 26 Aug 2002 16:50:06 -0500
From: Brian Foddy <[EMAIL PROTECTED]>
To: Dave Carrigan <[EMAIL PROTECTED]>
References: <[EMAIL PROTECTED]> <1029336677.4953.7.camel@buffy>
Dave,
I've finished my primary testing of these changes. Attached is
a patch file for auth_ldap 1.6.0 to allow it to search through nested
groups.
I added 2 new config directives:
AuthLDAPGroupSearchBaseDN to allow for a seperate group search base dn,
if not defined the normal basedn is used.
AuthLDAPNestedGroupSearch is a boolean flag whether to perform
nested group searches. default OFF.
I haven't yet started writing my inteneded application using these features
yet, so I may find additional bugs as I go along. But in the concentrated
testing I've done so far it seems to work well.
Again, I've done very little testing with cache behavior, but I hope hooking
in where I have the cache should not change. In our low volume hit environment
caching is not a big issue anyway.
Please look over these changes and give me feedback, thoughts, etc.
Brian Foddy
Dave Carrigan wrote:
On Tue, 2002-08-13 at 13:34, Brian Foddy wrote:
However, looking at auth_ldap I don't see any support for this (as you stated in your note). Has anything changed?At this point, there is no support for recursive groups. However, since
it's at the top of the most requested features, I will probably be
adding it in some future release (or accepting patches that do it, hint
hint :-)
Since I'm not doing any significant development on auth_ldap right now,
I really can't predict a time frame for this.
where SOCusersA & B belong to SOCUsers as nested groups. ButThis is probably the only way to do it, but as you say, it's not easy to
this doesn't look too easy to maintain either.
maintain. As more people start to use AD, the demand for recursive
groups is going to get larger, so it will be making it into auth_ldap
some time.
Regards,
Dave Carrigan
diff -c auth_ldap-1.6.0//auth_ldap.c auth_ldap-1.6.0_bkf//auth_ldap.c
*** auth_ldap-1.6.0//auth_ldap.c Wed Jul 4 09:38:03 2001
--- auth_ldap-1.6.0_bkf//auth_ldap.c Thu Aug 22 15:43:24 2002
***************
*** 386,392 ****
Further, assume that the userid passed by the client was `userj'. The
search filter will be (&(posixid=*)(uid=userj)).
*/
! #define FILTER_LENGTH MAX_STRING_LEN
void
build_filter(char *filtbuf,
request_rec *r,
--- 386,392 ----
Further, assume that the userid passed by the client was `userj'. The
search filter will be (&(posixid=*)(uid=userj)).
*/
!
void
build_filter(char *filtbuf,
request_rec *r,
***************
*** 971,977 ****
--- 971,994 ----
"If set to 'on', auth_ldap uses the DN that is retrieved from the server for"
"subsequent group comparisons. If set to 'off', auth_ldap uses the string"
"provided by the client directly. Defaults to 'on'."},
+
+ /* This syntax used for a direct set, the one below will call the function.
+ {"AuthLDAPGroupSearchBaseDN", ap_set_string_slot,
+ (void *)XtOffsetOf(auth_ldap_config_rec, groupBasedn), OR_AUTHCFG, TAKE1,
+ "Allows the setting of a different base DN for group searches. If unset, "
+ "the normal base DN is used"},
+ */
+ {"AuthLDAPGroupSearchBaseDN", auth_ldap_set_group_base_dn, NULL,
+ OR_AUTHCFG, TAKE1,
+ "Allows the setting of a different base DN for group searches. If unset, "
+ "the normal base DN is used"},
+ {"AuthLDAPNestedGroupSearch", ap_set_flag_slot,
+ (void *)XtOffsetOf(auth_ldap_config_rec, nestedgroupsearch), OR_AUTHCFG, FLAG,
+ "If set 'on', allows auth_ldap to recursively search group permissions for nested
+"
+ "membership. If set 'off' or unset, normal flat group membership is used. "
+ "Defaults to 'off'"},
+
#ifdef WITH_SSL
{"AuthLDAPCertDBPath", auth_ldap_set_certdbpath, NULL, RSRC_CONF, TAKE1,
"Specifies the file containing Certificate Authority certificates "
diff -c auth_ldap-1.6.0//auth_ldap.h auth_ldap-1.6.0_bkf//auth_ldap.h
*** auth_ldap-1.6.0//auth_ldap.h Sat Apr 7 15:20:17 2001
--- auth_ldap-1.6.0_bkf//auth_ldap.h Thu Aug 22 13:58:15 2002
***************
*** 59,64 ****
--- 59,66 ----
#include "auth_ldap_cache_mgr.h"
+ #define FILTER_LENGTH MAX_STRING_LEN
+
/* Backwards compatibility for Apache 1.2 */
#if APACHE_RELEASE < 1030000
#define auth_ldap_get_pw get_pw
***************
*** 144,150 ****
with LDAPv2, the const_cast casts away the constness, but won't under LDAPv3 */
#define const_cast(x) ((char *)(x))
#else
! #define const_cast(x) (x)
#endif /* LDAP_SDK_VERSION */
/*
--- 146,152 ----
with LDAPv2, the const_cast casts away the constness, but won't under LDAPv3 */
#define const_cast(x) ((char *)(x))
#else
! #define const_cast(x) ((char *)(x))
#endif /* LDAP_SDK_VERSION */
/*
***************
*** 259,271 ****
#ifdef HAVE_TLS
int starttls; /* True if StartTLS */
#endif
!
} auth_ldap_config_rec;
struct groupattr_entry {
char *name;
};
!
/*
On NT, ap_release_mutex returns the result of ReleaseMutex. On Unix,
--- 261,280 ----
#ifdef HAVE_TLS
int starttls; /* True if StartTLS */
#endif
!
! int nestedgroupsearch; /* flag to allow nested group searches */
! char *groupBasedn; /* the base dn to be used for group searches */
} auth_ldap_config_rec;
struct groupattr_entry {
char *name;
};
!
! typedef struct {
! char * dn;
! void * next;
! } result_dn_ll_type;
!
/*
On NT, ap_release_mutex returns the result of ReleaseMutex. On Unix,
***************
*** 326,331 ****
--- 335,341 ----
extern void *create_auth_ldap_config(pool *p, server_rec *s);
extern int auth_ldap_display_info(request_rec *r);
extern const char *auth_ldap_version;
+ extern const char *auth_ldap_set_group_base_dn(cmd_parms *, auth_ldap_config_rec *,
+char *);
int auth_ldap_authbind(const char *, request_rec *, url_node *);
int auth_ldap_comparedn(const char *, const char *, request_rec *, url_node *);
***************
*** 332,337 ****
--- 342,353 ----
int auth_ldap_compare(const char *, const char *, const char *,
request_rec *, ald_cache *);
int auth_ldap_connect_to_server(request_rec *r);
+ int auth_ldap_compare_grp (auth_ldap_config_rec *, char*, char*, char*,request_rec
+*);
+ int auth_ldap_recurse_groups (auth_ldap_config_rec *, char*, char*,
+char*,request_rec*);
+ int search_dn_ll (result_dn_ll_type *, char *);
+ result_dn_ll_type * new_ll_member (result_dn_ll_type *);
+ void build_group_filter (char*, char*, char*, char*);
+
void auth_ldap_free_connection(request_rec *r, int log);
void auth_ldap_log_reason(request_rec *r, const char *fmt, ...);
diff -c auth_ldap-1.6.0//auth_ldap_cache.c auth_ldap-1.6.0_bkf//auth_ldap_cache.c
*** auth_ldap-1.6.0//auth_ldap_cache.c Fri Feb 16 17:06:20 2001
--- auth_ldap-1.6.0_bkf//auth_ldap_cache.c Thu Aug 22 16:22:52 2002
***************
*** 364,372 ****
ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r,
"{%d} LDAP OP: compare", (int)getpid());
! if ((result = ldap_compare_s(sec->ldc->ldap, const_cast(dn),
const_cast(attrib), const_cast(value))) ==
LDAP_SERVER_DOWN) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r,
"{%d} Server is down; reconnecting and starting over",
(int)getpid());
auth_ldap_free_connection(r, 1);
--- 364,376 ----
ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r,
"{%d} LDAP OP: compare", (int)getpid());
! /* if ((result = auth_ldap_compare_grp(sec->ldc->ldap, const_cast(dn),
const_cast(attrib), const_cast(value))) ==
LDAP_SERVER_DOWN) {
+ */
+ if ((result = auth_ldap_compare_grp (sec, const_cast(dn),
+ const_cast(attrib), const_cast(value), r)) ==
+ LDAP_SERVER_DOWN) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r,
"{%d} Server is down; reconnecting and starting over",
(int)getpid());
auth_ldap_free_connection(r, 1);
***************
*** 388,393 ****
--- 392,638 ----
}
}
+
+ int auth_ldap_compare_grp (auth_ldap_config_rec *sec,
+ char* dn,
+ char* attr,
+ char* value,
+ request_rec *r)
+ {
+ int rc;
+
+ if ((!sec->nestedgroupsearch) || (strcmp (attr, "name") == 0))
+ rc = ldap_compare_s (sec->ldc->ldap, dn, attr, value);
+ else
+ rc = auth_ldap_recurse_groups (sec, dn, attr, value, r);
+
+ return rc;
+ }
+
+ int auth_ldap_recurse_groups (auth_ldap_config_rec *sec,
+ char* dn,
+ char* attr,
+ char* value,
+ request_rec *r)
+ {
+ LDAPMessage * ldap_results;
+ char filter [FILTER_LENGTH];
+ int found = LDAP_COMPARE_FALSE;
+ char * test_dn;
+ int result;
+ /*
+ Until we get a better config directive or something...
+ */
+ /* char group_base [] = "ou=groups,DC=tads,DC=nwa,DC=com"; */
+
+ result_dn_ll_type *head_ll, *current_ll, *tmp_ll;
+
+ /*
+ Ok, first we need to grab the primary groups the user directly belongs to.
+
+ Then for each primary group, recurse back up the tree until a NULL
+ is hit.
+
+ Of course we can jump out as soon as we hit the correct group. (dn)
+ */
+ memset (filter, 0, FILTER_LENGTH);
+
+ head_ll = current_ll = NULL;
+ /*
+ Build the first user to group query, after this,
+ the returned output will be sent back in again.
+
+ Use the raw call, on Solaris its very confusing which version I really
+have.
+ */
+ build_group_filter (filter, sec->basedn, sec->user, attr);
+ result =
+ ldap_search_s (sec->ldc->ldap, sec->groupBasedn, sec->scope, filter,
+NULL, 1, &ldap_results);
+
+ if (result != LDAP_SUCCESS) {
+ auth_ldap_log_reason(r, "LDAP recursive search for filter [%s]
+basedn[%s] failed: LDAP error: %s; URI %s",
+ filter, sec->groupBasedn, ldap_err2string(result), r->uri);
+ return DECLINED;
+ }
+ ldap_results = ldap_first_entry (sec->ldc->ldap, ldap_results);
+ if (ldap_results == NULL) {
+ /* no results found */
+ }
+
+ /*
+ Loop all our results (the original userid query)
+ */
+ do {
+ if ((test_dn = ldap_get_dn (sec->ldc->ldap, ldap_results)) == NULL)
+ continue;
+
+ if (strcasecmp (dn, test_dn) == 0) { /* we found our match */
+ found = LDAP_COMPARE_TRUE;
+ break;
+ }
+
+ if (search_dn_ll (head_ll, test_dn) == 1) /* this is a dupe, dont
+store */
+ continue;
+
+
+ if ((current_ll = new_ll_member (current_ll)) == NULL) {
+ /* error out, malloc error */
+ }
+ if (head_ll == NULL) head_ll = current_ll; /* store the head of the
+list */
+ current_ll->dn = test_dn;
+
+ } while ((ldap_results = ldap_next_entry (sec->ldc->ldap, ldap_results)) !=
+NULL);
+
+ free (ldap_results);
+
+ if (found == LDAP_COMPARE_FALSE) /* we did not get lucky with a first round
+hit */
+ {
+
+ /*
+ Now loop through in a semi-recursive manner all the returned results.
+ Why not make this a full recursive call?
+ Cuz I assume the LDAP connection will not handle nested
+queries,
+ and I don't want to open gobs of additional
+connections.
+ */
+
+ tmp_ll = head_ll;
+ while (tmp_ll) {
+ /* build a new query */
+ ap_snprintf (filter, FILTER_LENGTH,
+"(&(objectclass=group)(member=%s))",
+ tmp_ll->dn);
+
+ result = ldap_search_s (sec->ldc->ldap, sec->groupBasedn,
+sec->scope, filter, NULL, 1, &ldap_results);
+
+ if (result != LDAP_SUCCESS) {
+ auth_ldap_log_reason(r, "LDAP search for %s failed:
+LDAP error: %s; URI %s",
+ filter, ldap_err2string(result), r->uri);
+ return DECLINED;
+ }
+
+ ldap_results = ldap_first_entry (sec->ldc->ldap, ldap_results);
+
+ if (ldap_results == NULL) { /* no results */
+ tmp_ll = tmp_ll->next;
+ continue;
+ }
+
+
+ do {
+ if ((test_dn = ldap_get_dn (sec->ldc->ldap,
+ldap_results)) == NULL)
+ continue;
+
+ if ((current_ll = new_ll_member (current_ll)) == NULL)
+{
+ /* error out, malloc error */
+ }
+
+ current_ll->dn = ldap_get_dn (sec->ldc->ldap,
+ldap_results);
+
+ if (strcasecmp (dn, current_ll->dn) == 0) { /* we
+found our match */
+ found = LDAP_COMPARE_TRUE;
+ break;
+ }
+
+ if (search_dn_ll (head_ll, test_dn) == 1) /* this is
+a dupe, dont store */
+ continue;
+
+ } while ((ldap_results = ldap_next_entry (sec->ldc->ldap,
+ldap_results)) != NULL);
+
+ free (ldap_results);
+ tmp_ll = tmp_ll->next;
+ } /* end while link list */
+
+ } /* end if not found */
+
+ if (found == LDAP_COMPARE_FALSE) {
+ auth_ldap_log_reason(r, "LDAP recursive search for filter [%s]
+basedn[%s] Auth failed",
+ filter, sec->groupBasedn);
+ }
+
+ /*
+ Now we have a lot of cleanup to do, hopefully to eliminate
+ hung connections and memory leaks.
+ */
+ current_ll = head_ll;
+ while (current_ll) {
+ tmp_ll = current_ll;
+ current_ll = current_ll->next;
+
+ free (tmp_ll->dn); /* free the string */
+ tmp_ll->next = NULL; /* set pointer to NULL just for kicks */
+ free (tmp_ll); /* free the link list node */
+ }
+
+
+ return found; /* ldap_compare_s (sec->ldc->ldap, dn, attr, value); */
+ }
+
+
+ /*
+ function to search for a DN already located.
+ */
+ int search_dn_ll (result_dn_ll_type * start, char * compare)
+ {
+ result_dn_ll_type * ptr = start;
+
+ while (ptr)
+ {
+ if (strcasecmp (ptr->dn, compare) == 0) /* match */
+ {
+ return 1;
+ }
+ ptr = ptr->next;
+ }
+ return 0;
+ }
+
+ result_dn_ll_type * new_ll_member (result_dn_ll_type * ptr)
+ {
+ result_dn_ll_type * new_ptr;
+ /*
+ Initialize a link list to store the results.
+ */
+ new_ptr = (result_dn_ll_type *) malloc (sizeof (result_dn_ll_type));
+ if (new_ptr == NULL)
+ {
+ return NULL; /* fail out, malloc error. */
+ }
+ new_ptr->dn = NULL;
+ new_ptr->next = NULL;
+
+ if (ptr != NULL) ptr->next = new_ptr;
+ ptr = new_ptr;
+ return ptr;
+ }
+
+ void build_group_filter (char * input_filter, char* base, char * user, char * attr)
+ {
+ char *p, *q, *filtbuf_end;
+ char temp_user [FILTER_LENGTH];
+
+ memset (temp_user, 0, FILTER_LENGTH);
+ /*
+ Now add the client-supplied username to the filter, ensuring that any
+ LDAP filter metachars are escaped.
+ */
+ filtbuf_end = temp_user + sizeof (temp_user) - 1;
+
+ for (p = user, q=temp_user + strlen(temp_user);
+ *p && q < filtbuf_end; *q++ = *p++) {
+ if (strchr("*()\\", *p) != NULL) {
+ *q++ = '\\';
+ if (q >= filtbuf_end)
+ break;
+ }
+ }
+ *q = '\0';
+
+
+
+ ap_snprintf (input_filter, FILTER_LENGTH,
+"(&(objectclass=group)(%s=cn=%s,%s))",
+ attr,
+ user,
+ base);
+ }
+
/*
* Some definitions to help between various versions of apache.
*/
diff -c auth_ldap-1.6.0//auth_ldap_config.c auth_ldap-1.6.0_bkf//auth_ldap_config.c
*** auth_ldap-1.6.0//auth_ldap_config.c Wed Jul 4 09:40:00 2001
--- auth_ldap-1.6.0_bkf//auth_ldap_config.c Thu Aug 22 15:12:52 2002
***************
*** 38,43 ****
--- 38,44 ----
sec->bindpw = NULL;
sec->deref = always;
sec->group_attrib_is_dn = 1;
+ sec->nestedgroupsearch = 0; /* default to no nesting searches */
#ifdef AUTH_LDAP_FRONTPAGE_HACK
sec->frontpage_hack = 0;
***************
*** 111,116 ****
--- 112,118 ----
sec->host = urld->lud_host? ap_pstrdup(cmd->pool, urld->lud_host) : "localhost";
}
sec->basedn = urld->lud_dn? ap_pstrdup(cmd->pool, urld->lud_dn) : "";
+ sec->groupBasedn = sec->basedn; /* default to the same */
if (urld->lud_attrs && urld->lud_attrs[0]) {
sec->attribute = ap_pstrdup(cmd->pool, urld->lud_attrs[0]);
} else {
***************
*** 322,324 ****
--- 324,337 ----
return c;
}
+
+ const char *
+ auth_ldap_set_group_base_dn(cmd_parms *cmd, auth_ldap_config_rec *sec, char *arg)
+ {
+ if ((arg == NULL) || (arg[0] == 0))
+ sec->groupBasedn = sec->basedn;
+ else
+ sec->groupBasedn = arg;
+ return NULL;
+ }
+
