util_ldap.c revision ebe5305f8b22507374358f32b74d12fb50c05a25
61N/A/* Licensed to the Apache Software Foundation (ASF) under one or more 61N/A * contributor license agreements. See the NOTICE file distributed with 61N/A * this work for additional information regarding copyright ownership. 61N/A * The ASF licenses this file to You under the Apache License, Version 2.0 61N/A * (the "License"); you may not use this file except in compliance with 61N/A * the License. You may obtain a copy of the License at 61N/A * Unless required by applicable law or agreed to in writing, software 61N/A * distributed under the License is distributed on an "AS IS" BASIS, 61N/A * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 61N/A * See the License for the specific language governing permissions and 61N/A * limitations under the License. 61N/A * Original code from auth_ldap module for Apache v1.3: 61N/A * Copyright 1998, 1999 Enbridge Pipelines Inc. 61N/A * Copyright 1999-2001 Dave Carrigan /* Default define for ldap functions that need a SIZELIMIT but * XXX This should be removed once a supporting #define is * released through APR-Util. * This handler generates a status page about the current performance of * the LDAP cache. It is enabled as follows: * <Location /ldap-status> "<html><head><title>LDAP Cache Information</title></head>\n", r);
ap_rputs(
"<body bgcolor='#ffffff'><h1 align=center>LDAP Cache Information" /* ------------------------------------------------------------------ */ * Closes an LDAP connection by unlocking it. The next time * uldap_connection_find() is called this connection will be * Is it safe leaving bound connections floating around between the * different modules? Keeping the user bound is a performance boost, * but it is also a potential security problem - maybe. * For now we unbind the user when we finish with a connection, but /* mark our connection as available for reuse */ * Destroys an LDAP connection by unbinding and closing the connection to * the LDAP server. It is used to bring the connection back to a known * state after an error, and during pool cleanup. * Clean up an LDAP connection by unbinding and unlocking the connection. * This function is registered with the pool cleanup function - causing * the LDAP connections to be shut down cleanly on graceful restart. /* unbind and disconnect from the LDAP server */ /* free the username and password */ /* Since the host will include a port if the default port is not used, * always specify the default ports for the port parameter. This will * allow a host string that contains multiple hosts the ability to mix * some hosts with ports and some without. All hosts which do not * specify a port will use the default port. ldc->
reason =
"LDAP: ldap initialization failed";
/* always default to LDAP V3 */ /* set client certificates */ /* Set the alias dereferencing option */ /*XXX All of the #ifdef's need to be removed once apr-util 1.2 is released */ /* This is not a per-connection setting so just pass NULL for the Ldap connection handle */ "LDAP: Could not set the connection timeout");
* Connect to the LDAP server and binds. Does not connect if already * connected (i.e. ldc->ldap is non-NULL.) Does not bind if already bound. * Returns LDAP_SUCCESS on success; and an error code on failure /* sanity check for NULL */ /* If the connection is already bound, return ldc->
reason =
"LDAP: connection open successful (already bound)";
/* create the ldap session handle /* loop trying to bind up to 10 times if LDAP_SERVER_DOWN error is * returned. Break out of the loop on Success or any other error. * NOTE: Looping is probably not a great idea. If the server isn't * responding the chances it will respond after a few tries are poor. * However, the original code looped and it only happens on /* attempt to init the connection once again */ /* free the handle if there was an error ldc->
reason =
"LDAP: ldap_simple_bind_s() failed";
ldc->
reason =
"LDAP: connection open successful";
* Compare client certificate arrays. * Returns 1 on compare failure, 0 otherwise. /* arrays both NULL? if so, then equal */ /* arrays different length or either NULL? If so, then not equal */ /* run an actual comparison */ /* if we got here, the cert arrays were identical */ * Find an existing ldap connection struct that matches the * provided ldap connection parameters. * If not found in the cache, a new ldc structure will be allocated * from st->pool and returned to the caller. If found in the cache, * a pointer to the existing ldc structure will be returned. /* mutex lock this function */ /* Search for an exact connection match in the list that is not /* If this connection didn't match the criteria, then we * need to unlock the mutex so it is available to be reused. /* If nothing found, search again, but we don't care about the * binddn and bindpw this time. /* the bind credentials have changed */ /* If this connection didn't match the criteria, then we * need to unlock the mutex so it is available to be reused. /* artificially disable cache */ /* If no connection what found after the second search, we * Add the new connection entry to the linked list. Note that we * don't actually establish an LDAP connection yet; that happens * the first time authentication is requested. /* create the details to the pool in st */ "util_ldap: Failed to create memory pool");
/* The security mode after parsing the URL will always be either * APR_LDAP_NONE (ldap://) or APR_LDAP_SSL (ldaps://). * If the security setting is NONE, override it to the security * setting optionally supplied by the admin using LDAPTrustedMode /* save away a copy of the client cert list that is presently valid */ /* add the cleanup to the pool */ /* ------------------------------------------------------------------ */ * Compares two DNs to see if they're equal. The only way to do this correctly * is to search for the dn and then do ldap_get_dn() on the result. This should * match the initial dn, since it would have been also retrieved with * ldap_get_dn(). This is expensive, so if the configuration value * compare_dn_on_server is false, just does an ordinary strcmp. * The lock for the ldap cache should already be acquired. const char *
url,
const char *
dn,
/* get cache entry (or create one) */ /* unlock this read lock */ ldc->
reason =
"DN Comparison FALSE (direct strcmp())";
ldc->
reason =
"DN Comparison TRUE (direct strcmp())";
/* no - it's a server side compare */ /* is it in the compare cache? */ /* If it's in the cache, it's good */ /* unlock this read lock */ /* unlock this read lock */ /* make a server connection */ /* connect to server failed */ "(objectclass=*)",
NULL,
1,
ldc->
reason =
"DN Comparison ldap_search_ext_s() " "failed with server down";
/* search for reqdn failed - no match */ ldc->
reason =
"DN Comparison ldap_search_ext_s() failed";
/* compare unsuccessful */ ldc->
reason =
"DN Comparison FALSE (checked on server)";
/* compare successful - add to the compare cache */ ldc->
reason =
"DN Comparison TRUE (checked on server)";
* 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 * (require group compares) and (require user compares). Each compare has a different * cache node: require group includes the DN; require user does not because the * require user cache is owned by the const char *
url,
const char *
dn,
/* get cache entry (or create one) */ /* make a comparison to the cache */ /* ...but it is too old */ ldc->
reason =
"Comparison no such attribute (cached)";
ldc->
reason =
"Comparison undefined (cached)";
/* record the result code to return with the reason... */ /* and unlock this read lock */ /* unlock this read lock */ /* connection failed - try again */ ldc->
reason =
"ldap_compare_s() failed with server down";
/* compare completed; caching result */ /* If the node doesn't exist then insert it, otherwise just update * it with the last results ldc->
reason =
"Comparison true (adding to cache)";
ldc->
reason =
"Comparison false (adding to cache)";
ldc->
reason =
"Comparison no such attribute (adding to cache)";
* Does a recursive lookup operation to try to find a user within (cached) nested groups. * DON'T CALL THIS UNLESS YOU CALLED uldap_cache_compare FIRST!!!!! const char *
url,
const char *
dn,
* 1. Call uldap_cache_compare for each subgroupclass value to check the generic, * user-agnostic, cached group entry. This will create a new generic cache entry if there * wasn't one. If nothing returns LDAP_COMPARE_TRUE skip to step 5 since we have no groups. * 2. Lock The cache and get the generic cache entry. * 3. Check if there is already a subgrouplist in this generic group's cache entry. * A. If there is, go to step 4. * i) Use ldap_search to get the full list * of subgroup "members" (which may include non-group "members"). * ii) Use uldap_cache_compare to strip the list down to just groups. * iii) Lock and add this stripped down list to the cache of the generic group. * 4. Loop through the sgl and call uldap_cache_compare (using the user info) for each * subgroup to see if the subgroup contains the user and to get the subgroups added to the * cache (with user-afinity, if they aren't already there). * A. If the user is in the subgroup, then we'll be returning LDAP_COMPARE_TRUE. * B. if the user isn't in the subgroup (LDAP_COMPARE_FALSE via uldap_cache_compare) then * recursively call this function to get the sub-subgroups added... * 5. Cleanup local allocations. * 6. Return the final result. /* Stop looking at deeper levels of nested groups if we have reached the max. * Since we already checked the top-level group in uldap_cache_compare, we don't * need to check it again here - so if max_subgroup_depth is set to 0, we won't * check it (i.e. that is why we check < rather than <=). * We'll be calling uldap_cache_compare from here to check if the user is in the * next level before we recurse into that next level looking for more subgroups. /* 1. Check the "groupiness" of the specified basedn. Stopping at the first TRUE return. */ /* The dn we were handed doesn't seem to be a group, how can we check for SUB-groups if this ldc->
reason =
"DN failed group verification.";
/* 2. Find previously created cache entry and check if there is already a subgrouplist. */ /* make a comparison to the cache */ /* Didn't find it. This shouldn't happen since we just called uldap_cache_compare. */ ldc->
reason =
"check_subgroups failed to find cached element.";
/* Found the generic group entry... but the user isn't in this group or we wouldn't be here. */ /* Make a local copy of the subgroup list */ /* unlock this read lock */ /* If we get here, something is wrong. Caches should have been created and this group entry should be found in the cache. */ ldc->
reason =
"check_subgroups failed to find any caches.";
/* No cache entry had a processed SGL. Retrieve from LDAP server */ /* 3.B. The cache didn't have any subgrouplist yet. Go check for subgroups. */ /* try to do the search */ ldc->
reason =
"ldap_search_ext_s() for subgroups failed with server down";
/* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */ ldc->
reason =
"ldap_search_ext_s() for subgroups failed";
* Get values for the provided sub-group attributes. /* Get *all* matching "member" values from this group. */ * Now we are going to pare the subgroup members of this group to *just* * the subgroups, add them to the compare_nodep, and then proceed to check * the new level of subgroups. /* Check if this entry really is a group. */ /* It's a group, so add it to the array. */ /* We need to fill in tmp_local_subgroups using the data from LDAP */ /* Find the generic group cache entry and add the sgl. */ /* Didn't find it. This shouldn't happen since we just called uldap_cache_compare. */ ldc->
reason =
"check_subgroups failed to find the cache entry to add sub-group list to.";
/* overwrite SGL if it was previously updated between the last ** two times we looked at the cache /* We didn't find a single subgroup, next time save us from looking */ /* unlock this read lock */ /* tmp_local_sgl has either been created, or copied out of the cache */ /* If tmp_local_sgl is NULL, there are no subgroups to process and we'll return false */ /* 4. Now loop through the subgroupList and call uldap_cache_compare to check for the user. */ /* 4.A. We found the user in the subgroup. Return LDAP_COMPARE_TRUE. */ " Found the user in a subgroup (%s) at level %d of %d. (7).",
/* 4.B. We didn't find the user in this subgroup, so recurse into it and keep looking. */ /* Get the cache node for this url */ /* found entry in search cache... */ * Remove this item from the cache if its expired. If the sent * password doesn't match the storepassword, the entry will * be removed and readded later if the credentials pass /* ...but entry is too old */ /* ...and entry is valid */ ldc->
reason =
"Authentication successful (cached)";
/* unlock this read lock */ * At this point, there is no valid cached search, so lets do the search. * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here. ldc->
reason =
"ldap_search_ext_s() for user failed with server down";
/* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */ ldc->
reason =
"ldap_search_ext_s() for user failed";
* We should have found exactly one entry; to find a different ldc->
reason =
"User is not unique (search found two " /* Grab the dn, copy it into the pool, and free it again */ * 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 * 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) ldc->
reason =
"ldap_simple_bind_s() to check user credentials " "failed with server down";
/* failure? if so - return */ ldc->
reason =
"ldap_simple_bind_s() to check user credentials failed";
* We have just bound the connection to a different user and password * combination, which might be reused unintentionally next time this * connection is used from the connection pool. To ensure no confusion, * we mark the connection as unbound. * Get values for the provided attributes. * Add the new username to the search cache. /* Search again to make sure that another thread didn't ready insert * this node into the cache before we got here. If it does exist then /* Nothing in cache, insert new entry */ /* Entry in cache is invalid, remove it and insert new one */ /* Cache entry is valid, update lastbind */ * This function will return the DN of the entry matching userid. * It is used to get the DN in case some other module than mod_auth_ldap * has authenticated the user. * The function is basically a copy of uldap_cache_checkuserid * with password checking removed. /* Get the cache node for this url */ /* found entry in search cache... */ * Remove this item from the cache if its expired. /* ...but entry is too old */ /* ...and entry is valid */ /* unlock this read lock */ * At this point, there is no valid cached search, so lets do the search. * If LDAP operation fails due to LDAP_SERVER_DOWN, control returns here. ldc->
reason =
"ldap_search_ext_s() for user failed with server down";
/* if there is an error (including LDAP_NO_SUCH_OBJECT) return now */ ldc->
reason =
"ldap_search_ext_s() for user failed";
* We should have found exactly one entry; to find a different ldc->
reason =
"User is not unique (search found two " /* Grab the dn, copy it into the pool, and free it again */ * Get values for the provided attributes. * Add the new username to the search cache. /* Search again to make sure that another thread didn't ready insert * this node into the cache before we got here. If it does exist then /* Nothing in cache, insert new entry */ * Don't update lastbind on entries with bindpw because * we haven't verified that password. It's OK to update * the entry if there is no password in it. /* Cache entry is valid, update lastbind */ * Reports if ssl support is enabled * 1 = enabled, 0 = not enabled /* ---------------------------------------- */ "LDAP cache: Setting shared memory cache file to %s bytes.",
"[%" APR_PID_T_FMT "] ldap cache: Setting cache TTL to %ld microseconds.",
"[%" APR_PID_T_FMT "] ldap cache: Setting search cache size to %ld entries.",
"[%" APR_PID_T_FMT "] ldap cache: Setting operation cache TTL to %ld microseconds.",
"[%" APR_PID_T_FMT "] ldap cache: Setting operation cache size to %ld " * Parse the certificate type. * The type can be one of the following: * CA_DER, CA_BASE64, CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64, * CERT_KEY3_DB, CERT_NICKNAME, KEY_DER, KEY_BASE64 * If no matches are found, APR_LDAP_CA_TYPE_UNKNOWN is returned. /* Authority file in binary DER format */ /* Authority file in Base64 format */ /* Client cert file in DER format */ /* Client cert file in Base64 format */ /* Client cert file in PKCS#12 format */ /* Netscape client cert nickname */ /* Client cert key file in DER format */ /* Client cert key file in Base64 format */ /* Client cert key file in PKCS#12 format */ * Set LDAPTrustedGlobalCert. * This directive takes either two or three arguments: * - certificate file / directory / nickname * - certificate password (optional) * This directive may only be used globally. /* handle the certificate type */ "not recognised. It should be one " "of CA_DER, CA_BASE64, CA_CERT7_DB, " "CA_SECMOD, CERT_DER, CERT_BASE64, " "CERT_KEY3_DB, CERT_NICKNAME, " "KEY_DER, KEY_BASE64",
type);
return "Certificate type was not specified.";
"LDAP: SSL trusted global cert - %s (type %s)",
/* add the certificate to the global array */ /* if file is a file or path, fix the path */ "LDAP: Could not open SSL trusted certificate " return "Invalid global certificate file path";
* Set LDAPTrustedClientCert. * This directive takes either two or three arguments: * - certificate file / directory / nickname * - certificate password (optional) /* handle the certificate type */ "not recognised. It should be one " "of CERT_DER, CERT_BASE64, " "CERT_NICKNAME, CERT_PFX," "KEY_DER, KEY_BASE64, KEY_PFX",
"LDAPTrustedGlobalCert directive. " "Only CERT_DER, CERT_BASE64, " "CERT_NICKNAME, KEY_DER, and " "KEY_BASE64 may be used.",
type);
return "Certificate type was not specified.";
"LDAP: SSL trusted client cert - %s (type %s)",
/* add the certificate to the global array */ /* if file is a file or path, fix the path */ "LDAP: Could not open SSL client certificate " return "Invalid client certificate file path";
* This directive sets what encryption mode to use on a connection: * - STARTTLS (TLS encryption) "LDAP: SSL trusted mode - %s",
return "Invalid LDAPTrustedMode setting: must be one of NONE, " "LDAP: SSL verify server certificate - %s",
"[%" APR_PID_T_FMT "] ldap connection: Setting connection timeout to " "LDAP: Connection timout option not supported by the " /* Create a per vhost pool for mod_ldap to use, serialized with * st->mutex (also one per vhost). both are replicated by fork(), * no shared memory managed by either. /* The cache settings can not be modified in a virtual host since all server use the same /* These LDAP connection settings can not be overwritten in a virtual host. Once set in the base server, they must remain the same. None of the LDAP SDKs seem to be able to handle setting the verify_svr_cert flag on a per-connection basis. The OpenLDAP client appears to be able to handle the connection timeout per-connection but the Novell SDK cannot. Allowing the timeout to be set by each vhost is of little value so rather than trying to make special expections for one LDAP SDK, GLOBAL_ONLY is being enforced on this setting as well. */ /* util_ldap_post_config() will be called twice. Don't bother * going through all of the initialization on the first call * because it will just be thrown away.*/ /* If the cache file already exists then delete it. Otherwise we are * going to run into problems creating the shared memory. */ /* initializing cache if shared memory size is not zero and we already "LDAP cache: could not create shared memory segment");
"LDAP cache: failed to set mutex permissions");
/* merge config in all vhost */ "LDAP merging Shared Cache conf: shm=0x%pp rmm=0x%pp " "LDAP cache: LDAPSharedCacheSize is zero, disabling " * Initialize SSL support, and log the result for the benefit of the admin. * If SSL is not supported it is not necessarily an error, as the * application may not want to use it. "LDAP: SSL support available" );
"LDAP: SSL support unavailable%s%s",
"Failed to initialise global mutex %s in child process %" "Set the size of the shared memory cache (in bytes). Use " "0 to disable the shared memory cache. (default: 100000)"),
"Set the file name for the shared memory cache."),
"Set the maximum number of entries that are possible in the " "LDAP search cache. Use 0 for no limit. " "-1 disables the cache. (default: 1024)"),
"Set the maximum time (in seconds) that an item can be " "cached in the LDAP search cache. Use 0 for no limit. " "Set the maximum number of entries that are possible " "in the LDAP compare cache. Use 0 for no limit. " "Use -1 to disable the cache. (default: 1024)"),
"Set the maximum time (in seconds) that an item is cached " "in the LDAP operation cache. Use 0 for no limit. " "Takes three args; the file and/or directory containing " "the trusted CA certificates (and global client certs " "for Netware) used to validate the LDAP server. Second " "arg is the cert type for the first arg, one of CA_DER, " "CA_BASE64, CA_CERT7_DB, CA_SECMOD, CERT_DER, CERT_BASE64, " "CERT_KEY3_DB, CERT_NICKNAME, KEY_DER, or KEY_BASE64. " "Third arg is an optional passphrase if applicable."),
"Takes three args; the file and/or directory containing " "the client certificate, or certificate ID used to " "validate this LDAP client. Second arg is the cert type " "for the first arg, one of CA_DER, CA_BASE64, CA_CERT7_DB, " "CA_SECMOD, CERT_DER, CERT_BASE64, CERT_KEY3_DB, " "CERT_NICKNAME, KEY_DER, or KEY_BASE64. Third arg is an " "optional passphrase if applicable."),
"Specify the type of security that should be applied to " "an LDAP connection. One of; NONE, SSL or STARTTLS."),
"Set to 'ON' requires that the server certificate be verified " "before a secure LDAP connection can be establish. Default 'ON'"),
"Specify the LDAP socket connection timeout in seconds " NULL,
/* create dir config */ NULL,
/* merge dir config */