/* 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" "\n" "Searches are performed using the attribute and the filter combined. " "For example, assume that the\n" "LDAP URL is ldap://ldap.airius.com/ou=People, o=Airius?uid?sub?(posixid=*). " "Searches will\n" "be done using the filter (&((posixid=*))(uid=username)), " "where username\n" "is the user name passed by the HTTP client. The search will be a subtree " "search on the branch ou=People, o=Airius." }, {"AuthLDAPBindDN", ap_set_string_slot, (void *)XtOffsetOf(auth_ldap_config_rec, binddn), OR_AUTHCFG, TAKE1, "DN to use to bind to LDAP server. If not provided, will do an anonymous bind."}, {"AuthLDAPBindPassword", ap_set_string_slot, (void *)XtOffsetOf(auth_ldap_config_rec, bindpw), OR_AUTHCFG, TAKE1, "Password to use to bind to LDAP server. If not provided, will do an anonymous bind " "will be done."}, {"AuthLDAPRemoteUserIsDN", ap_set_flag_slot, (void *)XtOffsetOf(auth_ldap_config_rec, user_is_dn), OR_AUTHCFG, FLAG, "Set to 'on' to set the REMOTE_USER environment variable to be the full " "DN of the remote user. By default, this is set to off, meaning that " "the REMOTE_USER variable will contain whatever value the remote user sent."}, {"AuthLDAPAuthoritative", ap_set_flag_slot, (void *)XtOffsetOf(auth_ldap_config_rec, auth_authoritative), OR_AUTHCFG, FLAG, "Set to 'off' to allow access control to be passed along to lower modules if " "the UserID and/or group is not known to this module"}, {"AuthLDAPCompareDNOnServer", ap_set_flag_slot, (void *)XtOffsetOf(auth_ldap_config_rec, compare_dn_on_server), OR_AUTHCFG, FLAG, "Set to 'on' to force auth_ldap to do DN compares (for the \"require dn\" " "directive) using the server, and set it 'off' to do the compares locally " "(at the expense of possible false matches). See the documentation for " "a complete description of this option."}, {"AuthLDAPGroupAttribute", auth_ldap_add_group_attribute, NULL, OR_AUTHCFG, ITERATE, "A list of attributes used to define group membership - defaults to " "member and uniquemember"}, {"AuthLDAPGroupAttributeIsDN", ap_set_flag_slot, (void *)XtOffsetOf(auth_ldap_config_rec, group_attrib_is_dn), OR_AUTHCFG, FLAG, "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'."}, #ifdef WITH_SSL {"AuthLDAPCertDBPath", auth_ldap_set_certdbpath, NULL, RSRC_CONF, TAKE1, "Specifies the file containing Certificate Authority certificates " "for validating secure LDAP server certificates. This file must be the " "cert7.db database used by Netscape Communicator"}, #endif {"AuthLDAPCacheSize", auth_ldap_set_cache_size, NULL, RSRC_CONF, TAKE1, "Sets the maximum amount of memory in bytes that the LDAP search cache will use. " "Zero means no limit; -1 disables the cache. Defaults to 10KB."}, {"AuthLDAPCacheTTL", auth_ldap_set_cache_ttl, NULL, RSRC_CONF, TAKE1, "Sets the maximum time (in seconds) that an item can be cached in the LDAP " "search cache. Zero means no limit. Defaults to 600 seconds (10 minutes)."}, {"AuthLDAPOpCacheSize", auth_ldap_set_opcache_size, NULL, RSRC_CONF, TAKE1, "Sets the initial size of the LDAP operation cache (for bind and compare " "operations). Set to 0 to disable the cache. The default is 1024, which is " "sufficient to efficiently cache approximately 3000 distinct DNs."}, {"AuthLDAPOpCacheTTL", auth_ldap_set_opcache_ttl, NULL, RSRC_CONF, TAKE1, "Sets the maximum time (in seconds) that an item is cached in the LDAP " "operation cache. Zero means no limit. Defaults to 600 seconds (10 minutes)."}, {"AuthLDAPCacheCompareOps", auth_ldap_set_compare_flag, NULL, RSRC_CONF, TAKE1, "Set to no to disable caching of LDAP compare operations. Defaults to yes."}, {"AuthLDAPDereferenceAliases", auth_ldap_set_deref, NULL, OR_AUTHCFG, TAKE1, "Determines how aliases are handled during a search. Can bo one of the" "values \"never\", \"searching\", \"finding\", or \"always\". " "Defaults to always."}, {"AuthLDAPEnabled", ap_set_flag_slot, (void *)XtOffsetOf(auth_ldap_config_rec, enabled), OR_AUTHCFG, FLAG, "Set to off to disable auth_ldap, even if it's been enabled in a higher tree" }, #ifdef AUTH_LDAP_FRONTPAGE_HACK {"AuthLDAPFrontPageHack", ap_set_flag_slot, (void *)XtOffsetOf(auth_ldap_config_rec, frontpage_hack), OR_AUTHCFG, FLAG, "Set to 'on' to support Microsoft FrontPage"}, #endif #ifdef HAVE_TLS {"AuthLDAPStartTLS", ap_set_flag_slot, (void *)XtOffsetOf(auth_ldap_config_rec, starttls), OR_AUTHCFG, FLAG, "Set to 'on' to start TLS after connecting to the LDAP server." }, #endif /* HAVE_TLS */ {NULL} }; static const handler_rec auth_ldap_handlers[] = { {"auth-ldap-info", auth_ldap_display_info}, {NULL} }; module auth_ldap_module = { STANDARD_MODULE_STUFF, auth_ldap_init_module, /* initializer */ create_auth_ldap_dir_config, /* dir config creater */ NULL, /* dir merger --- default is to override */ create_auth_ldap_config, /* server config */ NULL, /* merge server config */ auth_ldap_cmds, /* command table */ auth_ldap_handlers, /* handlers */ NULL, /* filename translation */ ldap_authenticate_basic_user, /* check_user_id */ ldap_check_auth, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ NULL, /* logger */ NULL, /* header parser */ NULL, /* child_init */ NULL, /* child_exit */ NULL /* post read-request */ }; #ifdef NEED_EPRINTF /* More subroutines needed by GCC output code on some machines. */ /* Compile this one with gcc. */ /* Copyright (C) 1989, 1992, 1993, 1994, 1995 Free Software Foundation, Inc. This file is part if libgcc2.c, which is part of GNU CC. Auth_ldap just needs the _eprintf function. GNU CC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU CC 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 GNU CC; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #undef NULL /* Avoid errors if stdio.h and our stddef.h mismatch. */ #include /* This is used by the `assert' macro. */ void __eprintf (string, expression, line, filename) const char *string; const char *expression; int line; const char *filename; { fprintf (stderr, string, expression, line, filename); fflush (stderr); abort (); } #endif