/* Copyright © 1998, 1999 Enbridge Pipelines Inc. Copyright © 1999-2006 Dave Carrigan All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Apache itself. This module 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. The copyright holder of this module can not be held liable for any general, special, incidental or consequential damages arising out of the use of the module. $Id: auth_ldap.c,v 1.22 2001/07/04 14:38:03 dave Exp $ */ const char *auth_ldap_version = "1.6.1"; #define __AUTH_LDAP_C__ #include "auth_ldap.h" module MODULE_VAR_EXPORT auth_ldap_module; /* Backwards compatibility for Apache 1.2 */ #if APACHE_RELEASE < 1030000 int ldap_loglevel = 4; void ap_log_error(const char *file, int line, int level, const server_rec *s, const char *fmt, ...) { va_list args; if (level >= ldap_loglevel) return; fprintf(stderr, "(%s:%d) ", file, line); fflush(stderr); va_start(args, fmt); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); fflush(stderr); va_end(args); } #endif /* if APACHE_RELEASE */ /* * Compatibility for LDAPv2 libraries. Differences between LDAPv2 and * LDAPv3, as they affect this module * * LDAPv3 uses ldap_search_ext_s; LDAPv2 uses only basic ldap_search_s * * LDAPv3 uses ldap_memfree; LDAPv2 just uses free(). * * In this section, we just implement the LDAPv3 SDK functions that are * missing in LDAPv2. * */ #if LDAP_SDK_VERSION == 2 /* * LDAPv2 doesn't support extended search. Since auth_ldap doesn't use * it anyway, we just translate the extended search into a normal search. */ int ldap_search_ext_s(LDAP *ldap, char *base, int scope, char *filter, char **attrs, int attrsonly, void *servertrls, void *clientctrls, void *timeout, int sizelimit, LDAPMessage **res) { return ldap_search_s(ldap, base, scope, filter, attrs, attrsonly, res); } void ldap_memfree(void *p) { free(p); } #endif /* if LDAP_SDK_VERSION */ /* This function calls log_reason in 1.2, while it calls ap_log_error in 1.3+ */ void auth_ldap_log_reason(request_rec *r, const char *fmt, ...) { va_list args; char buf[MAX_STRING_LEN]; va_start(args, fmt); ap_vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); #if APACHE_RELEASE < 1030000 log_reason(buf, r->uri, r); #else ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r, "%s", buf); #endif } /* * Closes an LDAP connection by unbinding. Sets the boundas value for the * http connection config record and clears the bound dn string in the * global connection record. The next time connect_to_server is called, the * connection will be recreated. * * If the log parameter is set, adds a debug entry to the log that the * server was down and it's reconnecting. * * The mutex for the LDAPConnection should already be acquired. */ void auth_ldap_free_connection(request_rec *r, int log) { auth_ldap_config_rec *sec = (auth_ldap_config_rec *)ap_get_module_config(r->per_dir_config, &auth_ldap_module); if (log) ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Server is down; reconnecting and starting over", (int)getpid()); if (sec->ldc->ldap) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Freeing connection to ldap server(s) `%s'", (int)getpid(), sec->host); ldap_unbind_s(sec->ldc->ldap); sec->ldc->ldap = NULL; sec->ldc->boundas = bind_none; if (sec->ldc->bounddn) { free(sec->ldc->bounddn); sec->ldc->bounddn = NULL; } } } /* * Connect to the LDAP server and binds. Does not connect if already * connected (i.e.sec->ldc->ldap is non-NULL.) Does not bind if already bound. * Returns 1 on success; 0 on failure * * The mutex for the LDAPConnection should already be acquired. */ int auth_ldap_connect_to_server(request_rec *r) { int result; auth_ldap_config_rec *sec; auth_ldap_server_conf *conf; int failures = 0; int referrals = 0; ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Entering auth_ldap_connect_to_server", (int)getpid()); conf = (auth_ldap_server_conf *)ap_get_module_config(r->server->module_config, &auth_ldap_module); sec = (auth_ldap_config_rec *)ap_get_module_config(r->per_dir_config, &auth_ldap_module); start_over: if (failures++ > 10) { auth_ldap_log_reason(r, "Too many failures connecting to LDAP server"); return 0; } if (!sec->ldc->ldap) { sec->ldc->boundas = bind_none; if (sec->ldc->bounddn) { free(sec->ldc->bounddn); sec->ldc->bounddn = NULL; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Opening connection to ldap server(s) `%s'", (int)getpid(), sec->host); ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} LDAP OP: init", (int)getpid()); if ((sec->ldc->ldap = ldap_init(sec->host, sec->port)) == NULL) { extern int errno; auth_ldap_log_reason(r, "Could not connect to LDAP server: %s", strerror(errno)); return 0; } if(sec->version) { /* set version to use (ldap_set_option checks * if version is right) */ result = ldap_set_option(sec->ldc->ldap, LDAP_OPT_PROTOCOL_VERSION, &sec->version); if(result != LDAP_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r, "setting LDAP version %d failed", sec->version); } } #if LDAP_SDK_VERSION == 2 sec->ldc->ldap->ld_options &= ~LDAP_OPT_REFERRALS; /* Set the alias dereferencing option */ sec->ldc->ldap->ld_deref = sec->deref; #else result = ldap_set_option(sec->ldc->ldap, LDAP_OPT_REFERRALS, &referrals); if (result != LDAP_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r, "Setting LDAP referrals option failed: %s", ldap_err2string(result)); } result = ldap_set_option(sec->ldc->ldap, LDAP_OPT_DEREF, &(sec->deref)); if (result != LDAP_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r, "Setting LDAP dereference option failed: %s", ldap_err2string(result)); } #endif /* LDAP_SDK_VERSION */ #ifdef WITH_SSL if (sec->secure) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Initializing SSL for this connection", (int)getpid()); if (!conf->have_certdb) { auth_ldap_log_reason(r, "Secure LDAP requested, but no " "AuthLDAPCertDBPath directive in config"); return 0; } else { result = ldapssl_install_routines(sec->ldc->ldap); if (result != LDAP_SUCCESS) { auth_ldap_log_reason(r, "SSL initialization failed: %s", ldap_err2string(result)); return 0; } result = ldap_set_option(sec->ldc->ldap, LDAP_OPT_SSL, LDAP_OPT_ON); if (result != LDAP_SUCCESS) { auth_ldap_log_reason(r, "SSL option failed: %s", ldap_err2string(result)); return 0; } } } #endif #ifdef HAVE_TLS if (sec->starttls) { int version = LDAP_VERSION3; /* In auth_ldap_find_connection, we compare ldc->withtls to sec->starttls to see if we have a cache match. On the off chance that apache's config processing rotines set starttls to some other true value besides 1, we set it to 1 here to ensure that the comparison succeeds. */ /* Also we have to set the connection to use protocol version 3, since we're using TLS. */ if ((result = ldap_set_option(sec->ldc->ldap, LDAP_OPT_PROTOCOL_VERSION, &version)) != LDAP_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r, "Setting LDAP version option failed: %s", ldap_err2string(result)); } sec->starttls = 1; ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Starting TLS for this connection", (int)getpid()); result = ldap_start_tls_s( sec->ldc->ldap, NULL, NULL ); sec->ldc->withtls = 1; if (result != LDAP_SUCCESS) { auth_ldap_log_reason(r, "Start TLS failed: %s", ldap_err2string(result)); return 0; } } else { sec->ldc->withtls = 0; } #endif /* HAVE_TLS */ } /* * At this point the LDAP connection is guaranteed alive. If boundas says * that we're bound as system, we can just return. */ if (sec->ldc->boundas == bind_system) { return 1; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Binding to server `%s' as %s/%s", (int)getpid(), sec->host, sec->binddn? sec->binddn : "(null)", sec->bindpw? sec->bindpw : "(null)"); /* * Now bind with the username/password provided by the * configuration. It will be an anonymous bind if no u/p was * provided. */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} LDAP OP: simple bind", (int)getpid()); if ((result = ldap_simple_bind_s(sec->ldc->ldap, sec->binddn, sec->bindpw)) == LDAP_SERVER_DOWN) { auth_ldap_free_connection(r, 1); goto start_over; } if (result != LDAP_SUCCESS) { auth_ldap_free_connection(r, 0); auth_ldap_log_reason(r, "Could not bind to LDAP server `%s' as %s: %s", sec->host, sec->binddn? sec->binddn : "(null)", ldap_err2string(result)); return 0; } sec->ldc->bounddn = sec->binddn? strdup(sec->binddn) : NULL; sec->ldc->boundas = bind_system; return 1; } /* * Find an existing ldap connection struct that matches the * host. Create a new one if none are found. * Will set the ldc field of the sec struct. */ void auth_ldap_find_connection(auth_ldap_config_rec *sec, request_rec *r) { struct LDAPconnection *l, *p; /* To traverse the linked list */ auth_ldap_server_conf *conf; ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Entering auth_ldap_find_connection", (int)getpid()); conf = (auth_ldap_server_conf *)ap_get_module_config(r->server->module_config, &auth_ldap_module); GETMUTEX(conf->mtx); /* Find if this connection exists in the linked list yet */ for (l=conf->ldapconnections,p=NULL; l; l=l->next) { if (l->port == sec->port && strcmp(l->host, sec->host) == 0 #ifdef HAVE_TLS && l->withtls == sec->starttls #endif ) break; p = l; } if (l) { /* * Existing connection. Now determine if we need to rebind because * the last time this connection was used, it bound as a differert * dn than this time. */ if ((sec->binddn && !l->bounddn) || (!sec->binddn && l->bounddn) || (sec->binddn && l->bounddn && strcmp(sec->binddn, l->bounddn) != 0)) { l->boundas = bind_none; l->bounddn = NULL; } else { l->boundas = bind_system; } } else { /* Create a new connection entry in the linked list. Note that we don't actually establish an LDAP connection yet; that happens the first time authentication is requested. We allocate these connections out of the real heap, not from a pool. This is because the connections should last as long as possible. */ l = (struct LDAPconnection *)malloc(sizeof(struct LDAPconnection)); if (!l) { RELMUTEX(conf->mtx); return; } l->ldap = NULL; l->host = strdup(sec->host); l->port = sec->port; l->bounddn = NULL; l->next = NULL; l->mtx = ap_create_mutex(NULL); if (p) { p->next = l; } else { conf->ldapconnections = l; } l->boundas = bind_none; } sec->ldc = l; RELMUTEX(conf->mtx); } /* Build the search filter, or at least as much of the search filter that will fit in the buffer. We don't worry about the buffer not being able to hold the entire filter. If the buffer wasn't big enough to hold the filter, ldap_search_s will complain, but the only situation where this is likely to happen is if the client sent a really, really long username, most likely as part of an attack. The search filter consists of the filter provided with the URL, combined with a filter made up of the attribute provided with the URL, and the actual username passed by the HTTP client. For example, assume that the LDAP URL is ldap://ldap.airius.com/ou=People, o=Airius?uid??(posixid=*) 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, auth_ldap_config_rec *sec) { char *p, *q, *filtbuf_end; /* Create the first part of the filter, which consists of the config-supplied portions. */ ap_snprintf(filtbuf, FILTER_LENGTH, "(&(%s)(%s=", sec->filter, sec->attribute); /* Now add the client-supplied username to the filter, ensuring that any LDAP filter metachars are escaped. */ filtbuf_end = filtbuf + FILTER_LENGTH - 1; for (p = r->connection->user, q=filtbuf + strlen(filtbuf); *p && q < filtbuf_end; *q++ = *p++) { if (strchr("*()\\", *p) != NULL) { *q++ = '\\'; if (q >= filtbuf_end) break; } } *q = '\0'; /* Append the closing parens of the filter, unless doing so would overrun the buffer. */ if (q + 2 <= filtbuf_end) strcat(filtbuf, "))"); } url_node * auth_ldap_create_caches(request_rec *r, auth_ldap_config_rec *sec, auth_ldap_server_conf *conf) { url_node *curl; ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} inserting `%s' into URL cache", (int)getpid(), sec->url); curl = (url_node *)ap_pcalloc(r->pool, sizeof(url_node)); curl->url = sec->url; curl->search_cache = ald_create_cache(conf->search_cache_size, auth_ldap_search_node_hash, auth_ldap_search_node_compare, auth_ldap_search_node_copy, auth_ldap_search_node_free); curl->compare_cache = ald_create_cache(conf->compare_cache_size, auth_ldap_compare_node_hash, auth_ldap_compare_node_compare, auth_ldap_compare_node_copy, auth_ldap_compare_node_free); curl->dn_compare_cache = ald_create_cache(conf->compare_cache_size, auth_ldap_dn_compare_node_hash, auth_ldap_dn_compare_node_compare, auth_ldap_dn_compare_node_copy, auth_ldap_dn_compare_node_free); ald_cache_insert(auth_ldap_cache, curl); return curl; } int ldap_authenticate_basic_user(request_rec *r) { char filtbuf[FILTER_LENGTH]; auth_ldap_config_rec *sec = (auth_ldap_config_rec *)ap_get_module_config(r->per_dir_config, &auth_ldap_module); auth_ldap_server_conf *conf = (auth_ldap_server_conf *)ap_get_module_config(r->server->module_config, &auth_ldap_module); const char *sent_pw; int result; LDAPMessage *res, *entry; int count; char *dn; int failures = 0; url_node *curl; /* Cached URL node */ url_node curnode; search_node *search_nodep; /* Cached search node */ search_node the_search_node; time_t curtime; if (!sec->enabled) return DECLINED; ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Entering ldap_authenticate_basic_user", (int)getpid()); /* Basic sanity checks before any LDAP operations even happen. */ if (!sec->have_ldap_url) return DECLINED; /* There is a good AuthLDAPURL, right? */ if (sec->ldc == NULL) { auth_ldap_find_connection(sec, r); if (sec->ldc == NULL) { auth_ldap_log_reason(r, "Could not find/create LDAPconnection struct"); return sec->auth_authoritative? AUTH_REQUIRED : DECLINED; } } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} authenticate: using URL %s", (int)getpid(), sec->url); /* Get the password that the client sent */ if ((result = ap_get_basic_auth_pw(r, &sent_pw))) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} authenticate: result is %d", (int)getpid(), result); return result; } GETMUTEX(conf->mtx); /* Get the cache node for this url */ curnode.url = sec->url; curl = (url_node *)ald_cache_fetch(auth_ldap_cache, &curnode); if (curl == NULL) { curl = auth_ldap_create_caches(r, sec, conf); } sec->user = ap_pstrdup(r->pool, r->connection->user); the_search_node.username = sec->user; search_nodep = ald_cache_fetch(curl->search_cache, &the_search_node); if (search_nodep != NULL && search_nodep->bindpw) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} found entry in search cache for `%s'...", (int)getpid(), sec->user); time(&curtime); /* * Remove this item from the cache if its expired, or if the * sent password doesn't match the storepassword. */ if (curtime - search_nodep->lastbind > conf->search_cache_ttl) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} ...but entry is too old (%d seconds)", (int)getpid(), (int)(curtime - search_nodep->lastbind)); ald_cache_remove(curl->search_cache, search_nodep); } else if (strcmp(search_nodep->bindpw, sent_pw) != 0) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} ...but cached password doesn't match sent password", (int)getpid()); ald_cache_remove(curl->search_cache, search_nodep); } else { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} ...and entry is valid", (int)getpid()); sec->dn = ap_pstrdup(r->pool, search_nodep->dn); RELMUTEX(conf->mtx); return OK; } } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} entry for `%s' is not in the cache", (int)getpid(), sec->user); /* * At this point, there is no valid cached search, so lets do the search. */ build_filter(filtbuf, r, sec); GETMUTEX(sec->ldc->mtx); /* * If any LDAP operation fails due to LDAP_SERVER_DOWN, control returns here. */ start_over: if (failures++ > 10) { auth_ldap_log_reason(r, "Too many failures connecting to LDAP server"); RELMUTEX(conf->mtx); RELMUTEX(sec->ldc->mtx); return 0; } if (!auth_ldap_connect_to_server(r)) { RELMUTEX(conf->mtx); RELMUTEX(sec->ldc->mtx); return sec->auth_authoritative? AUTH_REQUIRED : DECLINED; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Peforming a search (scope=%d) with filter %s", (int)getpid(), sec->scope, filtbuf); ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} LDAP OP: search", (int)getpid()); if ((result = ldap_search_ext_s(sec->ldc->ldap, sec->basedn, sec->scope, filtbuf, NULL, 1, NULL, NULL, NULL, -1, &res)) == 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); goto start_over; } if (result != LDAP_SUCCESS) { auth_ldap_log_reason(r, "LDAP search for %s failed: LDAP error: %s; URI %s", filtbuf, ldap_err2string(result), r->uri); RELMUTEX(conf->mtx); RELMUTEX(sec->ldc->mtx); return sec->auth_authoritative? AUTH_REQUIRED : DECLINED; } /* We should have found exactly one entry; to find a different number is an error. */ count = ldap_count_entries(sec->ldc->ldap, res); if (count != 1) { auth_ldap_log_reason(r, "{%d} Search must return exactly 1 entry; found " "%d entries for search %s: URI %s", (int)getpid(), count, filtbuf, r->uri); if (sec->auth_authoritative) { ap_note_basic_auth_failure(r); } ldap_msgfree(res); RELMUTEX(conf->mtx); RELMUTEX(sec->ldc->mtx); return sec->auth_authoritative? AUTH_REQUIRED: DECLINED; } entry = ldap_first_entry(sec->ldc->ldap, res); /* Grab the dn, copy it into the pool, and free it again */ dn = ldap_get_dn(sec->ldc->ldap, entry); sec->dn = ap_pstrdup(r->pool, dn); ldap_memfree(dn); ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} DN returned from search is %s", (int)getpid(), sec->dn); /* A bind to the server with an empty password always succeeds, so we check to ensure that the password is not empty. This implies that users who actually do have empty passwords will never be able to authenticate with this module. I don't see this as a big problem. */ if (sec->dn == NULL || strlen(sent_pw) <= 0) { auth_ldap_log_reason(r, "AuthLDAP: user %s provided an empty password: %s", r->connection->user, r->uri); ap_note_basic_auth_failure(r); ldap_msgfree(res); RELMUTEX(conf->mtx); RELMUTEX(sec->ldc->mtx); return AUTH_REQUIRED; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Validating user `%s' via bind", (int)getpid(), sec->dn); sec->ldc->boundas = bind_user; /* * Attempt to bind with the retrieved dn and the password. If the bind * fails, it means that the password is wrong (the dn obviously * exists, since we just retrieved it) */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} LDAP OP: simple bind", (int)getpid()); if ((result = ldap_simple_bind_s(sec->ldc->ldap, sec->dn, const_cast(sent_pw))) == 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); goto start_over; } sec->ldc->boundas = bind_user; sec->ldc->bounddn = strdup(sec->dn); if (result != LDAP_SUCCESS) { auth_ldap_log_reason(r, "User bind as %s failed: LDAP error: %s; URI %s", sec->dn, ldap_err2string(result), r->uri); ap_note_basic_auth_failure(r); RELMUTEX(conf->mtx); RELMUTEX(sec->ldc->mtx); return AUTH_REQUIRED; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} authenticate: accepting", (int)getpid()); ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Adding user `%s' to the cache", (int)getpid(), sec->dn); ldap_msgfree(res); RELMUTEX(sec->ldc->mtx); /* * Add the new username to the search cache. */ the_search_node.username = sec->user; the_search_node.dn = sec->dn; the_search_node.bindpw = (char *)sent_pw; time(&the_search_node.lastbind); ald_cache_insert(curl->search_cache, &the_search_node); RELMUTEX(conf->mtx); return OK; } int ldap_check_auth(request_rec *r) { url_node *curl; /* Cached URL node */ url_node curnode; auth_ldap_config_rec *sec = (auth_ldap_config_rec *)ap_get_module_config(r->per_dir_config, &auth_ldap_module); auth_ldap_server_conf *conf = (auth_ldap_server_conf *)ap_get_module_config(r->server->module_config, &auth_ldap_module); int m = r->method_number; const array_header *reqs_arr = ap_requires(r); require_line *reqs = reqs_arr ? (require_line *)reqs_arr->elts : NULL; register int x; const char *t; char *w; int method_restricted = 0; if (!sec->enabled) return DECLINED; ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} Entering ldap_check_auth", (int)getpid()); if (!sec->have_ldap_url) return DECLINED; if (sec->ldc == NULL) { auth_ldap_find_connection(sec, r); if (sec->ldc == NULL) { auth_ldap_log_reason(r, "Could not find/create LDAPconnection struct"); return sec->auth_authoritative? AUTH_REQUIRED : DECLINED; } } /* * If there are no elements in the group attribute array, the default should be * member and uniquemember; populate the array now. */ if (sec->groupattr->nelts == 0) { struct groupattr_entry *grp; GETMUTEX(conf->mtx); grp = ap_push_array(sec->groupattr); grp->name = "member"; grp = ap_push_array(sec->groupattr); grp->name = "uniquemember"; RELMUTEX(conf->mtx); } if (sec->user_is_dn) r->connection->user = sec->dn; if (!reqs_arr) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} No requirements array", (int)getpid()); return sec->auth_authoritative? AUTH_REQUIRED : DECLINED; } GETMUTEX(conf->mtx); curnode.url = sec->url; curl = ald_cache_fetch(auth_ldap_cache, &curnode); if (curl == NULL) { curl = auth_ldap_create_caches(r, sec, conf); } /* Loop through the requirements array until there's no elements left, or something causes a return from inside the loop */ for(x=0; x < reqs_arr->nelts; x++) { if (! (reqs[x].method_mask & (1 << m))) continue; method_restricted = 1; t = reqs[x].requirement; w = ap_getword(r->pool, &t, ' '); if (strcmp(w, "valid-user") == 0) { #ifdef AUTH_LDAP_FRONTPAGE_HACK /* Valid user will always be true if we authenticated with ldap, but when using front page, valid user should only be true if he exists in the frontpage password file. This hack will get auth_ldap to look up the user in the the pw file to really be sure that he's valid. Naturally, it requires mod_auth to be compiled in, but if mod_auth wasn't in there, then the need for this hack wouldn't exist anyway. */ if (sec->frontpage_hack) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} deferring authorization to mod_auth (FP Hack)", (int)getpid()); RELMUTEX(conf->mtx); return OK; } else { #endif ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} agreeing to authenticate because user " "is valid-user", (int)getpid()); RELMUTEX(conf->mtx); return OK; #ifdef AUTH_LDAP_FRONTPAGE_HACK } #endif } else if (strcmp(w, "user") == 0) { if (sec->dn == NULL || strlen(sec->dn) == 0) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} The user's DN has not been defined; failing auth", (int)getpid()); return sec->auth_authoritative? AUTH_REQUIRED : DECLINED; } /* * First do a whole-line compare, in case it's something like * require user Babs Jensen */ if (auth_ldap_compare(sec->dn, sec->attribute, t, r, curl->compare_cache)) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} agreeing to authenticate because of " "require user directive", (int)getpid()); RELMUTEX(conf->mtx); return OK; } /* * Now break apart the line and compare each word on it */ while (t[0]) { w = ap_getword_conf(r->pool, &t); if (auth_ldap_compare(sec->dn, sec->attribute, w, r, curl->compare_cache)) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} agreeing to authenticate because of" "require user directive", (int)getpid()); RELMUTEX(conf->mtx); return OK; } } } else if (strcmp(w, "dn") == 0) { if (sec->dn == NULL || strlen(sec->dn) == 0) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} The user's DN has not been defined; failing auth", (int)getpid()); return sec->auth_authoritative? AUTH_REQUIRED : DECLINED; } if (auth_ldap_comparedn(sec->dn, t, r, curl)) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} agreeing to authenticate because of " "require dn directive", (int)getpid()); RELMUTEX(conf->mtx); return OK; } } else if (strcmp(w, "group") == 0) { struct groupattr_entry *ent = (struct groupattr_entry *) sec->groupattr->elts; int i; if (sec->group_attrib_is_dn) { if (sec->dn == NULL || strlen(sec->dn) == 0) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} The user's DN has not been defined; failing auth", (int)getpid()); return sec->auth_authoritative? AUTH_REQUIRED : DECLINED; } } else { if (sec->user == NULL || strlen(sec->user) == 0) { /* We weren't called in the authentication phase, so we didn't have a chance to set the user field. Do so now. */ sec->user = ap_pstrdup(r->pool, r->connection->user); } } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} testing for group membership in `%s'", (int)getpid(), t); for (i = 0; i < sec->groupattr->nelts; i++) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} testing for %s=%s", (int)getpid(), ent[i].name, sec->group_attrib_is_dn? sec->dn : sec->user); if (auth_ldap_compare(t, ent[i].name, sec->group_attrib_is_dn? sec->dn : sec->user, r, curl->compare_cache)) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} agreeing to authenticate because " "of group membership (attribute %s)", (int)getpid(), ent[i].name); RELMUTEX(conf->mtx); return OK; } } } } RELMUTEX(conf->mtx); if (!method_restricted) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} agreeing to authenticate because non-restricted", (int)getpid()); return OK; } if (!sec->auth_authoritative) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} declining to authenticate", (int)getpid()); return DECLINED; } ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r, "{%d} denying authentication", (int)getpid()); ap_note_basic_auth_failure (r); return AUTH_REQUIRED; } command_rec auth_ldap_cmds[] = { {"AuthLDAPVersion", parse_auth_ldap_version, NULL, OR_AUTHCFG, TAKE1, "Sets the LDAP version to use. Zero means use default version. " "Typical value is 2 or 3."}, {"AuthLDAPURL", parse_auth_ldap_url, NULL, OR_AUTHCFG, RAW_ARGS, "URL to define LDAP connection. This should be an RFC 2255 complaint\n" "URL of the form ldap://host[:port]/basedn[?attrib[?scope[?filter]]].\n" "