| /* |
| * lookup_ldap.c - Module for Linux automountd to access automount |
| * maps in LDAP directories. |
| * |
| * Copyright 2001-2003 Ian Kent <raven@themaw.net> |
| * |
| * This program 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, Inc., 675 Mass Ave, Cambridge MA 02139, |
| * USA; either version 2 of the License, or (at your option) any later |
| * version. |
| * |
| * This program 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. |
| * |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <ctype.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <time.h> |
| #include <signal.h> |
| #include <netinet/in.h> |
| #include <arpa/nameser.h> |
| #include <resolv.h> |
| #include <lber.h> |
| #include <libxml/tree.h> |
| #include <stdlib.h> |
| |
| #define MODULE_LOOKUP |
| #include "automount.h" |
| #include "nsswitch.h" |
| #include "lookup_ldap.h" |
| #include "base64.h" |
| |
| #define MAPFMT_DEFAULT "sun" |
| |
| #define MODPREFIX "lookup(ldap): " |
| |
| int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ |
| |
| #define ENV_LDAPTLS_CERT "LDAPTLS_CERT" |
| #define ENV_LDAPTLS_KEY "LDAPTLS_KEY" |
| |
| static struct ldap_schema common_schema[] = { |
| {"nisMap", "nisMapName", "nisObject", "cn", "nisMapEntry"}, |
| {"automountMap", "ou", "automount", "cn", "automountInformation"}, |
| {"automountMap", "automountMapName", "automount", "automountKey", "automountInformation"}, |
| }; |
| static unsigned int common_schema_count = sizeof(common_schema)/sizeof(struct ldap_schema); |
| |
| static struct ldap_schema amd_timestamp = { |
| "madmap", "amdmapName", "amdmapTimestamp", NULL, "amdmapTimestamp" |
| }; |
| |
| static struct ldap_schema amd_schema = { |
| "amdmap", "amdmapName", "amdmap", "amdmapKey", "amdmapValue" |
| }; |
| |
| /* |
| * Initialization and de-initialization of LDAP and OpenSSL must be |
| * always serialized to avoid corruption of context structures inside |
| * these libraries. |
| */ |
| pthread_mutex_t ldapinit_mutex = PTHREAD_MUTEX_INITIALIZER; |
| |
| struct ldap_search_params { |
| struct autofs_point *ap; |
| LDAP *ldap; |
| char *base; |
| char *query, **attrs; |
| struct berval *cookie; |
| ber_int_t pageSize; |
| int morePages; |
| ber_int_t totalCount; |
| LDAPMessage *result; |
| time_t age; |
| }; |
| |
| static int decode_percent_hack(const char *, char **); |
| |
| #ifdef WITH_SASL |
| static int set_env(unsigned logopt, const char *name, const char *val) |
| { |
| int ret = setenv(name, val, 1); |
| if (ret == -1) { |
| error(logopt, "failed to set config value for %s", name); |
| return 0; |
| } |
| return 1; |
| } |
| #endif |
| |
| #ifndef HAVE_LDAP_CREATE_PAGE_CONTROL |
| int ldap_create_page_control(LDAP *ldap, ber_int_t pagesize, |
| struct berval *cookie, char isCritical, |
| LDAPControl **output) |
| { |
| BerElement *ber; |
| int rc; |
| |
| if (!ldap || !output) |
| return LDAP_PARAM_ERROR; |
| |
| ber = ber_alloc_t(LBER_USE_DER); |
| if (!ber) |
| return LDAP_NO_MEMORY; |
| |
| if (ber_printf(ber, "{io}", pagesize, |
| (cookie && cookie->bv_val) ? cookie->bv_val : "", |
| (cookie && cookie->bv_val) ? cookie->bv_len : 0) |
| == LBER_ERROR) { |
| ber_free(ber, 1); |
| return LDAP_ENCODING_ERROR; |
| } |
| |
| rc = ldap_create_control(LDAP_CONTROL_PAGEDRESULTS, ber, isCritical, output); |
| |
| return rc; |
| } |
| #endif /* HAVE_LDAP_CREATE_PAGE_CONTROL */ |
| |
| #ifndef HAVE_LDAP_PARSE_PAGE_CONTROL |
| int ldap_parse_page_control(LDAP *ldap, LDAPControl **controls, |
| ber_int_t *totalcount, struct berval **cookie) |
| { |
| int i, rc; |
| BerElement *theBer; |
| LDAPControl *listCtrlp; |
| |
| for (i = 0; controls[i] != NULL; i++) { |
| if (strcmp(controls[i]->ldctl_oid, LDAP_CONTROL_PAGEDRESULTS) == 0) { |
| listCtrlp = controls[i]; |
| |
| theBer = ber_init(&listCtrlp->ldctl_value); |
| if (!theBer) |
| return LDAP_NO_MEMORY; |
| |
| rc = ber_scanf(theBer, "{iO}", totalcount, cookie); |
| if (rc == LBER_ERROR) { |
| ber_free(theBer, 1); |
| return LDAP_DECODING_ERROR; |
| } |
| |
| ber_free(theBer, 1); |
| return LDAP_SUCCESS; |
| } |
| } |
| |
| return LDAP_CONTROL_NOT_FOUND; |
| } |
| #endif /* HAVE_LDAP_PARSE_PAGE_CONTROL */ |
| |
| static void ldapinit_mutex_lock(void) |
| { |
| int status = pthread_mutex_lock(&ldapinit_mutex); |
| if (status) |
| fatal(status); |
| return; |
| } |
| |
| static void ldapinit_mutex_unlock(void) |
| { |
| int status = pthread_mutex_unlock(&ldapinit_mutex); |
| if (status) |
| fatal(status); |
| return; |
| } |
| |
| static void uris_mutex_lock(struct lookup_context *ctxt) |
| { |
| int status = pthread_mutex_lock(&ctxt->uris_mutex); |
| if (status) |
| fatal(status); |
| return; |
| } |
| |
| static void uris_mutex_unlock(struct lookup_context *ctxt) |
| { |
| int status = pthread_mutex_unlock(&ctxt->uris_mutex); |
| if (status) |
| fatal(status); |
| return; |
| } |
| |
| int bind_ldap_simple(unsigned logopt, LDAP *ldap, const char *uri, struct lookup_context *ctxt) |
| { |
| int rv; |
| |
| if (ctxt->auth_required == LDAP_AUTH_USESIMPLE) |
| rv = ldap_simple_bind_s(ldap, ctxt->user, ctxt->secret); |
| else if (ctxt->version == 2) |
| rv = ldap_simple_bind_s(ldap, ctxt->base, NULL); |
| else |
| rv = ldap_simple_bind_s(ldap, NULL, NULL); |
| |
| if (rv != LDAP_SUCCESS) { |
| if (!ctxt->uris) { |
| crit(logopt, MODPREFIX |
| "Unable to bind to the LDAP server: " |
| "%s, error %s", ctxt->server ? "" : "(default)", |
| ldap_err2string(rv)); |
| } else { |
| info(logopt, MODPREFIX "Unable to bind to the LDAP server: " |
| "%s, error %s", uri, ldap_err2string(rv)); |
| } |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int __unbind_ldap_connection(unsigned logopt, |
| struct ldap_conn *conn, |
| struct lookup_context *ctxt) |
| { |
| int rv = LDAP_SUCCESS; |
| |
| if (ctxt->use_tls == LDAP_TLS_RELEASE) |
| ctxt->use_tls = LDAP_TLS_INIT; |
| #ifdef WITH_SASL |
| if (ctxt->auth_required & LDAP_NEED_AUTH) |
| autofs_sasl_unbind(conn, ctxt); |
| /* No, sasl_dispose does not release the ldap connection |
| * unless it's using sasl EXTERNAL |
| */ |
| #endif |
| if (conn->ldap) { |
| rv = ldap_unbind_ext(conn->ldap, NULL, NULL); |
| conn->ldap = NULL; |
| } |
| if (rv != LDAP_SUCCESS) |
| error(logopt, "unbind failed: %s", ldap_err2string(rv)); |
| |
| return rv; |
| } |
| |
| int unbind_ldap_connection(unsigned logopt, |
| struct ldap_conn *conn, |
| struct lookup_context *ctxt) |
| { |
| int rv; |
| |
| ldapinit_mutex_lock(); |
| rv = __unbind_ldap_connection(logopt, conn, ctxt); |
| ldapinit_mutex_unlock(); |
| |
| return rv; |
| } |
| |
| LDAP *init_ldap_connection(unsigned logopt, const char *uri, struct lookup_context *ctxt) |
| { |
| LDAP *ldap = NULL; |
| struct timeval timeout = { ctxt->timeout, 0 }; |
| struct timeval net_timeout = { ctxt->network_timeout, 0 }; |
| int rv; |
| |
| ctxt->version = 3; |
| |
| /* Initialize the LDAP context. */ |
| rv = ldap_initialize(&ldap, uri); |
| if (rv != LDAP_OPT_SUCCESS) { |
| info(logopt, MODPREFIX |
| "couldn't initialize LDAP connection to %s", |
| uri ? uri : "default"); |
| return NULL; |
| } |
| |
| /* Use LDAPv3 */ |
| rv = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ctxt->version); |
| if (rv != LDAP_OPT_SUCCESS) { |
| /* fall back to LDAPv2 */ |
| ldap_unbind_ext(ldap, NULL, NULL); |
| rv = ldap_initialize(&ldap, uri); |
| if (rv != LDAP_OPT_SUCCESS) { |
| crit(logopt, MODPREFIX "couldn't initialize LDAP"); |
| return NULL; |
| } |
| ctxt->version = 2; |
| } |
| |
| |
| if (ctxt->timeout != -1) { |
| /* Set synchronous call timeout */ |
| rv = ldap_set_option(ldap, LDAP_OPT_TIMEOUT, &timeout); |
| if (rv != LDAP_OPT_SUCCESS) |
| info(logopt, MODPREFIX |
| "failed to set synchronous call timeout to %d", |
| timeout.tv_sec); |
| } |
| |
| /* Sane network timeout */ |
| rv = ldap_set_option(ldap, LDAP_OPT_NETWORK_TIMEOUT, &net_timeout); |
| if (rv != LDAP_OPT_SUCCESS) |
| info(logopt, MODPREFIX "failed to set connection timeout to %d", |
| net_timeout.tv_sec); |
| |
| if (ctxt->use_tls) { |
| if (ctxt->version == 2) { |
| if (ctxt->tls_required) { |
| error(logopt, MODPREFIX |
| "TLS required but connection is version 2"); |
| ldap_unbind_ext(ldap, NULL, NULL); |
| return NULL; |
| } |
| return ldap; |
| } |
| |
| rv = ldap_start_tls_s(ldap, NULL, NULL); |
| if (rv != LDAP_SUCCESS) { |
| ldap_unbind_ext(ldap, NULL, NULL); |
| if (ctxt->tls_required) { |
| error(logopt, MODPREFIX |
| "TLS required but START_TLS failed: %s", |
| ldap_err2string(rv)); |
| return NULL; |
| } |
| ctxt->use_tls = LDAP_TLS_DONT_USE; |
| ldap = init_ldap_connection(logopt, uri, ctxt); |
| if (ldap) |
| ctxt->use_tls = LDAP_TLS_INIT; |
| return ldap; |
| } |
| ctxt->use_tls = LDAP_TLS_RELEASE; |
| } |
| |
| return ldap; |
| } |
| |
| static int get_query_dn(unsigned logopt, LDAP *ldap, struct lookup_context *ctxt, const char *class, const char *key) |
| { |
| char buf[MAX_ERR_BUF]; |
| char *query, *dn, *qdn = NULL; |
| LDAPMessage *result = NULL, *e; |
| char *attrs[2]; |
| struct berval **value; |
| int scope; |
| int rv, l; |
| |
| attrs[0] = (char *) key; |
| attrs[1] = NULL; |
| |
| if (!ctxt->mapname && !ctxt->base) { |
| error(logopt, MODPREFIX "no master map to lookup"); |
| return 0; |
| } |
| |
| /* Build a query string. */ |
| l = strlen("(objectclass=)") + strlen(class) + 1; |
| if (ctxt->mapname) |
| l += strlen(key) + strlen(ctxt->mapname) + strlen("(&(=))"); |
| |
| query = malloc(l); |
| if (query == NULL) { |
| char *estr = strerror_r(errno, buf, sizeof(buf)); |
| crit(logopt, MODPREFIX "malloc: %s", estr); |
| return NSS_STATUS_UNAVAIL; |
| } |
| |
| /* |
| * If we have a master mapname construct a query using it |
| * otherwise assume the base dn will catch it. |
| */ |
| if (ctxt->mapname) { |
| if (sprintf(query, "(&(objectclass=%s)(%s=%.*s))", class, |
| key, (int) strlen(ctxt->mapname), ctxt->mapname) >= l) { |
| debug(logopt, |
| MODPREFIX "error forming query string"); |
| free(query); |
| return 0; |
| } |
| scope = LDAP_SCOPE_SUBTREE; |
| } else { |
| if (sprintf(query, "(objectclass=%s)", class) >= l) { |
| debug(logopt, |
| MODPREFIX "error forming query string"); |
| free(query); |
| return 0; |
| } |
| scope = LDAP_SCOPE_SUBTREE; |
| } |
| |
| dn = NULL; |
| if (!ctxt->sdns) { |
| rv = ldap_search_s(ldap, ctxt->base, |
| scope, query, attrs, 0, &result); |
| if ((rv != LDAP_SUCCESS) || !result) { |
| error(logopt, |
| MODPREFIX "query failed for %s: %s", |
| query, ldap_err2string(rv)); |
| if (result) |
| ldap_msgfree(result); |
| free(query); |
| return 0; |
| } |
| |
| e = ldap_first_entry(ldap, result); |
| if (e && (value = ldap_get_values_len(ldap, e, key))) { |
| ldap_value_free_len(value); |
| dn = ldap_get_dn(ldap, e); |
| debug(logopt, MODPREFIX "found query dn %s", dn); |
| } else { |
| debug(logopt, |
| MODPREFIX "query succeeded, no matches for %s", |
| query); |
| ldap_msgfree(result); |
| free(query); |
| return 0; |
| } |
| } else { |
| struct ldap_searchdn *this = ctxt->sdns; |
| |
| debug(logopt, MODPREFIX "check search base list"); |
| |
| result = NULL; |
| while (this) { |
| rv = ldap_search_s(ldap, this->basedn, |
| scope, query, attrs, 0, &result); |
| if ((rv == LDAP_SUCCESS) && result) { |
| debug(logopt, MODPREFIX |
| "found search base under %s", |
| this->basedn); |
| |
| e = ldap_first_entry(ldap, result); |
| if (e && (value = ldap_get_values_len(ldap, e, key))) { |
| ldap_value_free_len(value); |
| dn = ldap_get_dn(ldap, e); |
| debug(logopt, MODPREFIX "found query dn %s", dn); |
| break; |
| } else { |
| debug(logopt, |
| MODPREFIX "query succeeded, no matches for %s", |
| query); |
| ldap_msgfree(result); |
| result = NULL; |
| } |
| } else { |
| error(logopt, |
| MODPREFIX "query failed for search dn %s: %s", |
| this->basedn, ldap_err2string(rv)); |
| if (result) { |
| ldap_msgfree(result); |
| result = NULL; |
| } |
| } |
| |
| this = this->next; |
| } |
| |
| if (!result) { |
| error(logopt, |
| MODPREFIX "failed to find query dn under search base dns"); |
| free(query); |
| return 0; |
| } |
| } |
| |
| free(query); |
| if (dn) { |
| qdn = strdup(dn); |
| ldap_memfree(dn); |
| } |
| ldap_msgfree(result); |
| if (!qdn) |
| return 0; |
| |
| uris_mutex_lock(ctxt); |
| if (ctxt->qdn) |
| free(ctxt->qdn); |
| ctxt->qdn = qdn; |
| uris_mutex_unlock(ctxt); |
| |
| return 1; |
| } |
| |
| static struct ldap_schema *alloc_common_schema(struct ldap_schema *s) |
| { |
| struct ldap_schema *schema; |
| char *mc, *ma, *ec, *ea, *va; |
| |
| mc = strdup(s->map_class); |
| if (!mc) |
| return NULL; |
| |
| ma = strdup(s->map_attr); |
| if (!ma) { |
| free(mc); |
| return NULL; |
| } |
| |
| ec = strdup(s->entry_class); |
| if (!ec) { |
| free(mc); |
| free(ma); |
| return NULL; |
| } |
| |
| ea = strdup(s->entry_attr); |
| if (!ea) { |
| free(mc); |
| free(ma); |
| free(ec); |
| return NULL; |
| } |
| |
| va = strdup(s->value_attr); |
| if (!va) { |
| free(mc); |
| free(ma); |
| free(ec); |
| free(ea); |
| return NULL; |
| } |
| |
| schema = malloc(sizeof(struct ldap_schema)); |
| if (!schema) { |
| free(mc); |
| free(ma); |
| free(ec); |
| free(ea); |
| free(va); |
| return NULL; |
| } |
| |
| schema->map_class = mc; |
| schema->map_attr = ma; |
| schema->entry_class = ec; |
| schema->entry_attr = ea; |
| schema->value_attr = va; |
| |
| return schema; |
| } |
| |
| static int find_query_dn(unsigned logopt, LDAP *ldap, struct lookup_context *ctxt) |
| { |
| struct ldap_schema *schema; |
| unsigned int i; |
| |
| if (ctxt->schema) |
| return 0; |
| |
| if (ctxt->format & MAP_FLAG_FORMAT_AMD) { |
| schema = alloc_common_schema(&amd_schema); |
| if (!schema) { |
| error(logopt, MODPREFIX "failed to allocate schema"); |
| return 0; |
| } |
| ctxt->schema = schema; |
| return 1; |
| } |
| |
| for (i = 0; i < common_schema_count; i++) { |
| const char *class = common_schema[i].map_class; |
| const char *key = common_schema[i].map_attr; |
| if (get_query_dn(logopt, ldap, ctxt, class, key)) { |
| schema = alloc_common_schema(&common_schema[i]); |
| if (!schema) { |
| error(logopt, MODPREFIX "failed to allocate schema"); |
| return 0; |
| } |
| ctxt->schema = schema; |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int do_bind(unsigned logopt, struct ldap_conn *conn, |
| const char *uri, struct lookup_context *ctxt) |
| { |
| char *host = NULL, *nhost; |
| int rv; |
| |
| #ifdef WITH_SASL |
| debug(logopt, MODPREFIX "auth_required: %d, sasl_mech %s", |
| ctxt->auth_required, ctxt->sasl_mech); |
| |
| if (ctxt->auth_required & LDAP_NEED_AUTH) { |
| rv = autofs_sasl_bind(logopt, conn, ctxt); |
| debug(logopt, MODPREFIX "autofs_sasl_bind returned %d", rv); |
| } else { |
| rv = bind_ldap_simple(logopt, conn->ldap, uri, ctxt); |
| debug(logopt, MODPREFIX "ldap simple bind returned %d", rv); |
| } |
| #else |
| rv = bind_ldap_simple(logopt, conn->ldap, uri, ctxt); |
| debug(logopt, MODPREFIX "ldap simple bind returned %d", rv); |
| #endif |
| |
| if (rv != 0) |
| return 0; |
| |
| rv = ldap_get_option(conn->ldap, LDAP_OPT_HOST_NAME, &host); |
| if (rv != LDAP_SUCCESS || !host) { |
| debug(logopt, "failed to get hostname for connection"); |
| return 0; |
| } |
| |
| nhost = strdup(host); |
| if (!nhost) { |
| debug(logopt, "failed to alloc context for hostname"); |
| return 0; |
| } |
| ldap_memfree(host); |
| |
| uris_mutex_lock(ctxt); |
| if (!ctxt->cur_host) { |
| ctxt->cur_host = nhost; |
| if (!(ctxt->format & MAP_FLAG_FORMAT_AMD)) { |
| /* Check if schema defined in conf first time only */ |
| ctxt->schema = defaults_get_schema(); |
| } |
| } else { |
| /* If connection host has changed update */ |
| if (!strcmp(ctxt->cur_host, nhost)) |
| free(nhost); |
| else { |
| free(ctxt->cur_host); |
| ctxt->cur_host = nhost; |
| } |
| } |
| uris_mutex_unlock(ctxt); |
| |
| return 1; |
| } |
| |
| static int do_connect(unsigned logopt, struct ldap_conn *conn, |
| const char *uri, struct lookup_context *ctxt) |
| { |
| char *cur_host = NULL; |
| int ret = NSS_STATUS_SUCCESS; |
| |
| #ifdef WITH_SASL |
| if (ctxt->extern_cert && ctxt->extern_key) { |
| set_env(logopt, ENV_LDAPTLS_CERT, ctxt->extern_cert); |
| set_env(logopt, ENV_LDAPTLS_KEY, ctxt->extern_key); |
| } |
| #endif |
| |
| conn->ldap = init_ldap_connection(logopt, uri, ctxt); |
| if (!conn->ldap) { |
| ret = NSS_STATUS_UNAVAIL; |
| goto out; |
| } |
| |
| uris_mutex_lock(ctxt); |
| if (ctxt->cur_host) |
| cur_host = ctxt->cur_host; |
| uris_mutex_unlock(ctxt); |
| |
| if (!do_bind(logopt, conn, uri, ctxt)) { |
| __unbind_ldap_connection(logopt, conn, ctxt); |
| ret = NSS_STATUS_UNAVAIL; |
| goto out; |
| } |
| |
| /* If the lookup schema and the query dn are set and the |
| * ldap host hasn't changed return. |
| */ |
| uris_mutex_lock(ctxt); |
| if (ctxt->schema && ctxt->qdn && (cur_host == ctxt->cur_host)) { |
| uris_mutex_unlock(ctxt); |
| goto out; |
| } |
| uris_mutex_unlock(ctxt); |
| |
| /* |
| * If the schema isn't defined in the configuration then check for |
| * presence of a map dn with a the common schema. Then calculate the |
| * base dn for searches. |
| */ |
| if (!ctxt->schema) { |
| if (!find_query_dn(logopt, conn->ldap, ctxt)) { |
| __unbind_ldap_connection(logopt, conn, ctxt); |
| ret = NSS_STATUS_NOTFOUND; |
| warn(logopt, |
| MODPREFIX "failed to find valid query dn"); |
| goto out; |
| } |
| } else if (!(ctxt->format & MAP_FLAG_FORMAT_AMD)) { |
| const char *class = ctxt->schema->map_class; |
| const char *key = ctxt->schema->map_attr; |
| if (!get_query_dn(logopt, conn->ldap, ctxt, class, key)) { |
| __unbind_ldap_connection(logopt, conn, ctxt); |
| ret = NSS_STATUS_NOTFOUND; |
| error(logopt, MODPREFIX "failed to get query dn"); |
| goto out; |
| } |
| } |
| |
| out: |
| return ret; |
| } |
| |
| static unsigned long get_amd_timestamp(struct lookup_context *ctxt) |
| { |
| struct ldap_conn conn; |
| LDAP *ldap; |
| LDAPMessage *result = NULL, *e; |
| char *query; |
| int scope = LDAP_SCOPE_SUBTREE; |
| char *map, *class, *value; |
| char *attrs[2]; |
| struct berval **bvValues; |
| unsigned long timestamp = 0; |
| int rv, l, ql; |
| |
| memset(&conn, 0, sizeof(struct ldap_conn)); |
| rv = do_connect(LOGOPT_ANY, &conn, ctxt->server, ctxt); |
| if (rv != NSS_STATUS_SUCCESS) |
| return 0; |
| ldap = conn.ldap; |
| |
| map = amd_timestamp.map_attr; |
| class = amd_timestamp.entry_class; |
| value = amd_timestamp.value_attr; |
| |
| attrs[0] = value; |
| attrs[1] = NULL; |
| |
| /* Build a query string. */ |
| l = strlen(class) + |
| strlen(map) + strlen(ctxt->mapname) + 21; |
| |
| query = malloc(l); |
| if (query == NULL) { |
| char buf[MAX_ERR_BUF]; |
| char *estr = strerror_r(errno, buf, sizeof(buf)); |
| crit(LOGOPT_ANY, MODPREFIX "malloc: %s", estr); |
| return 0; |
| } |
| |
| /* |
| * Look for an entry in class under ctxt-base |
| * whose entry is equal to qKey. |
| */ |
| ql = sprintf(query, "(&(objectclass=%s)(%s=%s))", |
| class, map, ctxt->mapname); |
| if (ql >= l) { |
| error(LOGOPT_ANY, |
| MODPREFIX "error forming query string"); |
| free(query); |
| return 0; |
| } |
| |
| rv = ldap_search_s(ldap, ctxt->base, scope, query, attrs, 0, &result); |
| if ((rv != LDAP_SUCCESS) || !result) { |
| crit(LOGOPT_ANY, MODPREFIX "timestamp query failed %s", query); |
| unbind_ldap_connection(LOGOPT_ANY, &conn, ctxt); |
| if (result) |
| ldap_msgfree(result); |
| free(query); |
| return 0; |
| } |
| |
| e = ldap_first_entry(ldap, result); |
| if (!e) { |
| debug(LOGOPT_ANY, |
| MODPREFIX "got answer, but no entry for timestamp"); |
| ldap_msgfree(result); |
| unbind_ldap_connection(LOGOPT_ANY, &conn, ctxt); |
| free(query); |
| return CHE_MISSING; |
| } |
| |
| while (e) { |
| char *v_val; |
| char *endptr; |
| |
| bvValues = ldap_get_values_len(ldap, e, value); |
| if (!bvValues || !*bvValues) { |
| debug(LOGOPT_ANY, |
| MODPREFIX "no value found in timestamp"); |
| goto next; |
| } |
| |
| /* There should be one value for a timestamp */ |
| v_val = bvValues[0]->bv_val; |
| |
| timestamp = strtol(v_val, &endptr, 0); |
| if ((errno == ERANGE && |
| (timestamp == LONG_MAX || timestamp == LONG_MIN)) || |
| (errno != 0 && timestamp == 0)) { |
| debug(LOGOPT_ANY, |
| MODPREFIX "invalid value in timestamp"); |
| free(query); |
| return 0; |
| } |
| |
| if (endptr == v_val) { |
| debug(LOGOPT_ANY, |
| MODPREFIX "no digits found in timestamp"); |
| free(query); |
| return 0; |
| } |
| |
| if (*endptr != '\0') { |
| warn(LOGOPT_ANY, MODPREFIX |
| "characters found after number: %s", endptr); |
| warn(LOGOPT_ANY, |
| MODPREFIX "timestamp may be invalid"); |
| } |
| |
| ldap_value_free_len(bvValues); |
| break; |
| next: |
| ldap_value_free_len(bvValues); |
| e = ldap_next_entry(ldap, e); |
| } |
| |
| ldap_msgfree(result); |
| unbind_ldap_connection(LOGOPT_ANY, &conn, ctxt); |
| free(query); |
| |
| return timestamp; |
| } |
| |
| static int connect_to_server(unsigned logopt, struct ldap_conn *conn, |
| const char *uri, struct lookup_context *ctxt) |
| { |
| int ret; |
| |
| ret = do_connect(logopt, conn, uri, ctxt); |
| if (ret != NSS_STATUS_SUCCESS) { |
| warn(logopt, |
| MODPREFIX "couldn't connect to server %s", |
| uri ? uri : "default"); |
| } |
| |
| return ret; |
| } |
| |
| static int find_dc_server(unsigned logopt, struct ldap_conn *conn, |
| const char *uri, struct lookup_context *ctxt) |
| { |
| char *str, *tok, *ptr = NULL; |
| int ret = NSS_STATUS_UNAVAIL; |
| |
| str = strdup(uri); |
| if (!str) |
| return ret; |
| |
| tok = strtok_r(str, " ", &ptr); |
| while (tok) { |
| const char *this = (const char *) tok; |
| int rv; |
| |
| debug(logopt, "trying server uri %s", this); |
| rv = connect_to_server(logopt, conn, this, ctxt); |
| if (rv == NSS_STATUS_SUCCESS) { |
| info(logopt, "connected to uri %s", this); |
| free(str); |
| return rv; |
| } |
| if (rv == NSS_STATUS_NOTFOUND) |
| ret = NSS_STATUS_NOTFOUND; |
| tok = strtok_r(NULL, " ", &ptr); |
| } |
| |
| free(str); |
| |
| return ret; |
| } |
| |
| static int find_server(unsigned logopt, |
| struct ldap_conn *conn, struct lookup_context *ctxt) |
| { |
| struct ldap_uri *this = NULL; |
| struct list_head *p, *first; |
| struct dclist *dclist; |
| char *uri = NULL; |
| int ret = NSS_STATUS_UNAVAIL; |
| |
| uris_mutex_lock(ctxt); |
| dclist = ctxt->dclist; |
| if (!ctxt->uri) |
| first = ctxt->uris; |
| else |
| first = &ctxt->uri->list; |
| uris_mutex_unlock(ctxt); |
| |
| |
| /* Try each uri, save point in server list upon success */ |
| p = first->next; |
| while(p != first) { |
| int rv; |
| |
| /* Skip list head */ |
| if (p == ctxt->uris) { |
| p = p->next; |
| continue; |
| } |
| this = list_entry(p, struct ldap_uri, list); |
| if (!strstr(this->uri, ":///")) { |
| uri = strdup(this->uri); |
| debug(logopt, "trying server uri %s", uri); |
| rv = connect_to_server(logopt, conn, uri, ctxt); |
| if (rv == NSS_STATUS_SUCCESS) { |
| ret = NSS_STATUS_SUCCESS; |
| info(logopt, "connected to uri %s", uri); |
| free(uri); |
| break; |
| } |
| if (rv == NSS_STATUS_NOTFOUND) |
| ret = NSS_STATUS_NOTFOUND; |
| } else { |
| if (dclist) |
| uri = strdup(dclist->uri); |
| else { |
| struct dclist *tmp; |
| tmp = get_dc_list(logopt, this->uri); |
| if (!tmp) { |
| p = p->next; |
| continue; |
| } |
| dclist = tmp; |
| uri = strdup(dclist->uri); |
| } |
| rv = find_dc_server(logopt, conn, uri, ctxt); |
| if (rv == NSS_STATUS_SUCCESS) { |
| ret = NSS_STATUS_SUCCESS; |
| free(uri); |
| break; |
| } |
| if (rv == NSS_STATUS_NOTFOUND) |
| ret = NSS_STATUS_NOTFOUND; |
| } |
| free(uri); |
| uri = NULL; |
| if (dclist) { |
| free_dclist(dclist); |
| dclist = NULL; |
| } |
| p = p->next; |
| } |
| |
| uris_mutex_lock(ctxt); |
| if (conn->ldap) |
| ctxt->uri = this; |
| if (dclist) { |
| if (!ctxt->dclist) |
| ctxt->dclist = dclist; |
| else { |
| if (ctxt->dclist != dclist) { |
| free_dclist(ctxt->dclist); |
| ctxt->dclist = dclist; |
| } |
| } |
| } |
| uris_mutex_unlock(ctxt); |
| |
| return ret; |
| } |
| |
| static int do_reconnect(unsigned logopt, |
| struct ldap_conn *conn, struct lookup_context *ctxt) |
| { |
| int ret = NSS_STATUS_UNAVAIL; |
| int dcrv = NSS_STATUS_SUCCESS; |
| int rv = NSS_STATUS_SUCCESS; |
| |
| ldapinit_mutex_lock(); |
| if (ctxt->server || !ctxt->uris) { |
| ret = do_connect(logopt, conn, ctxt->server, ctxt); |
| #ifdef WITH_SASL |
| /* Dispose of the sasl authentication connection and try again. */ |
| if (ctxt->auth_required & LDAP_NEED_AUTH && |
| ret != NSS_STATUS_SUCCESS && ret != NSS_STATUS_NOTFOUND) { |
| autofs_sasl_dispose(conn, ctxt); |
| ret = connect_to_server(logopt, conn, |
| ctxt->server, ctxt); |
| } |
| #endif |
| ldapinit_mutex_unlock(); |
| return ret; |
| } |
| |
| if (ctxt->dclist) { |
| dcrv = find_dc_server(logopt, conn, ctxt->dclist->uri, ctxt); |
| if (dcrv == NSS_STATUS_SUCCESS) { |
| ldapinit_mutex_unlock(); |
| return dcrv; |
| } |
| } |
| |
| uris_mutex_lock(ctxt); |
| if (ctxt->dclist) { |
| if (!conn->ldap || ctxt->dclist->expire < monotonic_time(NULL)) { |
| free_dclist(ctxt->dclist); |
| ctxt->dclist = NULL; |
| } |
| /* Make sure we don't skip the domain spec */ |
| ctxt->uri = NULL; |
| uris_mutex_unlock(ctxt); |
| goto find_server; |
| } |
| uris_mutex_unlock(ctxt); |
| |
| if (!ctxt->uri) |
| goto find_server; |
| |
| rv = do_connect(logopt, conn, ctxt->uri->uri, ctxt); |
| #ifdef WITH_SASL |
| /* |
| * Dispose of the sasl authentication connection and try the |
| * current server again before trying other servers in the list. |
| */ |
| if (ctxt->auth_required & LDAP_NEED_AUTH && |
| rv != NSS_STATUS_SUCCESS && rv != NSS_STATUS_NOTFOUND) { |
| autofs_sasl_dispose(conn, ctxt); |
| rv = connect_to_server(logopt, conn, ctxt->uri->uri, ctxt); |
| } |
| #endif |
| if (rv == NSS_STATUS_SUCCESS) { |
| ldapinit_mutex_unlock(); |
| return rv; |
| } |
| |
| /* Failed to connect, try to find a new server */ |
| |
| find_server: |
| #ifdef WITH_SASL |
| autofs_sasl_dispose(conn, ctxt); |
| #endif |
| |
| /* Current server failed, try the rest or dc connection */ |
| ret = find_server(logopt, conn, ctxt); |
| if (ret != NSS_STATUS_SUCCESS) { |
| if (ret == NSS_STATUS_NOTFOUND || |
| dcrv == NSS_STATUS_NOTFOUND || |
| rv == NSS_STATUS_NOTFOUND) |
| ret = NSS_STATUS_NOTFOUND; |
| error(logopt, MODPREFIX "failed to find available server"); |
| } |
| ldapinit_mutex_unlock(); |
| |
| return ret; |
| } |
| |
| int get_property(unsigned logopt, xmlNodePtr node, const char *prop, char **value) |
| { |
| xmlChar *ret; |
| xmlChar *property = (xmlChar *) prop; |
| |
| if (!(ret = xmlGetProp(node, property))) { |
| *value = NULL; |
| return 0; |
| } |
| |
| if (!(*value = strdup((char *) ret))) { |
| logerr(MODPREFIX "strdup failed with %d", errno); |
| xmlFree(ret); |
| return -1; |
| } |
| |
| xmlFree(ret); |
| return 0; |
| } |
| |
| /* |
| * For plain text, login and digest-md5 authentication types, we need |
| * user and password credentials. |
| */ |
| int authtype_requires_creds(const char *authtype) |
| { |
| #ifdef WITH_SASL |
| if (!strncmp(authtype, "PLAIN", strlen("PLAIN")) || |
| !strncmp(authtype, "DIGEST-MD5", strlen("DIGEST-MD5")) || |
| !strncmp(authtype, "LOGIN", strlen("LOGIN"))) |
| return 1; |
| #endif |
| return 0; |
| } |
| |
| /* |
| * Returns: |
| * -1 -- The permission on the file are not correct or |
| * the xml document was mal-formed |
| * 0 -- The file was non-existent |
| * the file was empty |
| * the file contained valid data, which was filled into |
| * ctxt->sasl_mech, ctxt->user, and ctxt->secret |
| * |
| * The idea is that a -1 return value should abort the program. A 0 |
| * return value requires more checking. If ctxt->authtype is filled in, |
| * then no further action is necessary. If it is not, the caller is free |
| * to then use another method to determine how to connect to the server. |
| */ |
| int parse_ldap_config(unsigned logopt, struct lookup_context *ctxt) |
| { |
| int ret = 0, fallback = 0; |
| unsigned int auth_required = LDAP_AUTH_NOTREQUIRED; |
| unsigned int tls_required = 0, use_tls = 0; |
| struct stat st; |
| xmlDocPtr doc = NULL; |
| xmlNodePtr root = NULL; |
| char *authrequired, *auth_conf, *authtype; |
| char *user = NULL, *secret = NULL; |
| char *extern_cert = NULL, *extern_key = NULL; |
| char *client_princ = NULL, *client_cc = NULL; |
| char *usetls, *tlsrequired; |
| |
| authtype = user = secret = NULL; |
| |
| auth_conf = (char *) defaults_get_auth_conf_file(); |
| if (!auth_conf) { |
| error(logopt, |
| MODPREFIX "failed to get auth config file name."); |
| return 0; |
| } |
| |
| /* |
| * Here we check that the config file exists, and that we have |
| * permission to read it. The XML library does not specify why a |
| * parse happens to fail, so we have to do all of this checking |
| * beforehand. |
| */ |
| memset(&st, 0, sizeof(st)); |
| if (stat(auth_conf, &st) == -1 || st.st_size == 0) { |
| /* Auth config doesn't exist so disable TLS and auth */ |
| if (errno == ENOENT) { |
| ctxt->auth_conf = auth_conf; |
| ctxt->use_tls = LDAP_TLS_DONT_USE; |
| ctxt->tls_required = LDAP_TLS_DONT_USE; |
| ctxt->auth_required = LDAP_AUTH_NOTREQUIRED; |
| ctxt->sasl_mech = NULL; |
| ctxt->user = NULL; |
| ctxt->secret = NULL; |
| ctxt->client_princ = NULL; |
| return 0; |
| } |
| error(logopt, |
| MODPREFIX "stat(2) failed with error %s.", |
| strerror(errno)); |
| return 0; |
| } |
| |
| if (!S_ISREG(st.st_mode) || |
| st.st_uid != 0 || st.st_gid != 0 || |
| (st.st_mode & 0x01ff) != 0600) { |
| error(logopt, MODPREFIX |
| "Configuration file %s exists, but is not usable. " |
| "Please make sure that it is owned by root, group " |
| "is root, and the mode is 0600.", |
| auth_conf); |
| return -1; |
| } |
| |
| doc = xmlParseFile(auth_conf); |
| if (!doc) { |
| error(logopt, MODPREFIX |
| "xmlParseFile failed for %s.", auth_conf); |
| goto out; |
| } |
| |
| root = xmlDocGetRootElement(doc); |
| if (!root) { |
| debug(logopt, MODPREFIX |
| "empty xml document (%s).", auth_conf); |
| fallback = 1; |
| goto out; |
| } |
| |
| if (xmlStrcmp(root->name, (const xmlChar *)"autofs_ldap_sasl_conf")) { |
| error(logopt, MODPREFIX |
| "The root node of the XML document %s is not " |
| "autofs_ldap_sasl_conf.", auth_conf); |
| goto out; |
| } |
| |
| ret = get_property(logopt, root, "usetls", &usetls); |
| if (ret != 0) { |
| error(logopt, |
| MODPREFIX |
| "Failed read the usetls property from " |
| "the configuration file %s.", auth_conf); |
| goto out; |
| } |
| |
| if (!usetls || ctxt->port == LDAPS_PORT) |
| use_tls = LDAP_TLS_DONT_USE; |
| else { |
| if (!strcasecmp(usetls, "yes")) |
| use_tls = LDAP_TLS_INIT; |
| else if (!strcasecmp(usetls, "no")) |
| use_tls = LDAP_TLS_DONT_USE; |
| else { |
| error(logopt, |
| MODPREFIX |
| "The usetls property must have value " |
| "\"yes\" or \"no\"."); |
| ret = -1; |
| goto out; |
| } |
| free(usetls); |
| } |
| |
| ret = get_property(logopt, root, "tlsrequired", &tlsrequired); |
| if (ret != 0) { |
| error(logopt, |
| MODPREFIX |
| "Failed read the tlsrequired property from " |
| "the configuration file %s.", auth_conf); |
| goto out; |
| } |
| |
| if (!tlsrequired) |
| tls_required = LDAP_TLS_DONT_USE; |
| else { |
| if (!strcasecmp(tlsrequired, "yes")) |
| tls_required = LDAP_TLS_REQUIRED; |
| else if (!strcasecmp(tlsrequired, "no")) |
| tls_required = LDAP_TLS_DONT_USE; |
| else { |
| error(logopt, |
| MODPREFIX |
| "The tlsrequired property must have value " |
| "\"yes\" or \"no\"."); |
| ret = -1; |
| goto out; |
| } |
| free(tlsrequired); |
| } |
| |
| ret = get_property(logopt, root, "authrequired", &authrequired); |
| if (ret != 0) { |
| error(logopt, |
| MODPREFIX |
| "Failed read the authrequired property from " |
| "the configuration file %s.", auth_conf); |
| goto out; |
| } |
| |
| if (!authrequired) |
| auth_required = LDAP_AUTH_NOTREQUIRED; |
| else { |
| if (!strcasecmp(authrequired, "yes")) |
| auth_required = LDAP_AUTH_REQUIRED; |
| else if (!strcasecmp(authrequired, "no")) |
| auth_required = LDAP_AUTH_NOTREQUIRED; |
| else if (!strcasecmp(authrequired, "autodetect")) |
| auth_required = LDAP_AUTH_AUTODETECT; |
| else if (!strcasecmp(authrequired, "simple")) |
| auth_required = LDAP_AUTH_USESIMPLE; |
| else { |
| error(logopt, |
| MODPREFIX |
| "The authrequired property must have value " |
| "\"yes\", \"no\", \"autodetect\", or \"simple\"."); |
| ret = -1; |
| goto out; |
| } |
| free(authrequired); |
| } |
| |
| ret = get_property(logopt, root, "authtype", &authtype); |
| if (ret != 0) { |
| error(logopt, |
| MODPREFIX |
| "Failed read the authtype property from the " |
| "configuration file %s.", auth_conf); |
| goto out; |
| } |
| |
| if (auth_required == LDAP_AUTH_USESIMPLE || |
| (authtype && authtype_requires_creds(authtype))) { |
| char *s1 = NULL, *s2 = NULL; |
| ret = get_property(logopt, root, "user", &user); |
| ret |= get_property(logopt, root, "secret", &s1); |
| ret |= get_property(logopt, root, "encoded_secret", &s2); |
| if (ret != 0 || (!user || (!s1 && !s2))) { |
| auth_fail: |
| error(logopt, |
| MODPREFIX |
| "%s authentication type requires a username " |
| "and a secret. Please fix your configuration " |
| "in %s.", authtype, auth_conf); |
| free(authtype); |
| if (user) |
| free(user); |
| if (s1) |
| free(s1); |
| if (s2) |
| free(s2); |
| |
| ret = -1; |
| goto out; |
| } |
| if (!s2) |
| secret = s1; |
| else { |
| char dec_buf[120]; |
| int dec_len = base64_decode(s2, dec_buf, 119); |
| if (dec_len <= 0) |
| goto auth_fail; |
| secret = strdup(dec_buf); |
| if (!secret) |
| goto auth_fail; |
| if (s1) |
| free(s1); |
| if (s2) |
| free(s2); |
| } |
| } else if (auth_required == LDAP_AUTH_REQUIRED && |
| (authtype && !strncmp(authtype, "EXTERNAL", 8))) { |
| #ifdef WITH_SASL |
| ret = get_property(logopt, root, "external_cert", &extern_cert); |
| ret |= get_property(logopt, root, "external_key", &extern_key); |
| /* |
| * For EXTERNAL auth to function we need a client certificate |
| * and and certificate key. The ca certificate used to verify |
| * the server certificate must also be set correctly in the |
| * global configuration as the connection must be encrypted |
| * and the server and client certificates must have been |
| * verified for the EXTERNAL method to be offerred by the |
| * server. If the cert and key have not been set in the autofs |
| * configuration they must be set in the ldap rc file. |
| */ |
| if (ret != 0 || !extern_cert || !extern_key) { |
| if (extern_cert) |
| free(extern_cert); |
| if (extern_key) |
| free(extern_key); |
| } |
| #endif |
| } |
| |
| /* |
| * We allow the admin to specify the principal to use for the |
| * client. The default is "autofsclient/hostname@REALM". |
| */ |
| (void)get_property(logopt, root, "clientprinc", &client_princ); |
| (void)get_property(logopt, root, "credentialcache", &client_cc); |
| |
| ctxt->auth_conf = auth_conf; |
| ctxt->use_tls = use_tls; |
| ctxt->tls_required = tls_required; |
| ctxt->auth_required = auth_required; |
| ctxt->sasl_mech = authtype; |
| if (!authtype && (auth_required & LDAP_AUTH_REQUIRED)) |
| ctxt->auth_required |= LDAP_AUTH_AUTODETECT; |
| ctxt->user = user; |
| ctxt->secret = secret; |
| ctxt->client_princ = client_princ; |
| ctxt->client_cc = client_cc; |
| #ifdef WITH_SASL |
| ctxt->extern_cert = extern_cert; |
| ctxt->extern_key = extern_key; |
| #endif |
| |
| debug(logopt, MODPREFIX |
| "ldap authentication configured with the following options:"); |
| debug(logopt, MODPREFIX |
| "use_tls: %u, " |
| "tls_required: %u, " |
| "auth_required: %u, " |
| "sasl_mech: %s", |
| use_tls, tls_required, auth_required, authtype); |
| if (authtype && !strncmp(authtype, "EXTERNAL", 8)) { |
| debug(logopt, MODPREFIX "external cert: %s", |
| extern_cert ? extern_cert : "ldap default"); |
| debug(logopt, MODPREFIX "external key: %s ", |
| extern_key ? extern_key : "ldap default"); |
| } else { |
| debug(logopt, MODPREFIX |
| "user: %s, " |
| "secret: %s, " |
| "client principal: %s " |
| "credential cache: %s", |
| user, secret ? "specified" : "unspecified", |
| client_princ, client_cc); |
| } |
| out: |
| xmlFreeDoc(doc); |
| |
| if (fallback) |
| return 0; |
| |
| return ret; |
| } |
| |
| /* |
| * Take an input string as specified in the master map, and break it |
| * down into a server name and basedn. |
| */ |
| static int parse_server_string(unsigned logopt, const char *url, struct lookup_context *ctxt) |
| { |
| char buf[MAX_ERR_BUF], *tmp = NULL, proto[9]; |
| const char *ptr, *name; |
| int l, al_len; |
| |
| memset(proto, 0, 9); |
| ptr = url; |
| |
| debug(logopt, MODPREFIX |
| "Attempting to parse LDAP information from string \"%s\".", ptr); |
| |
| ctxt->port = LDAP_PORT; |
| if (!strncmp(ptr, "ldap:", 5) || !strncmp(ptr, "ldaps:", 6)) { |
| if (*(ptr + 4) == 's') { |
| ctxt->port = LDAPS_PORT; |
| memcpy(proto, ptr, 6); |
| strcat(proto, "//"); |
| ptr += 6; |
| } else { |
| memcpy(proto, ptr, 5); |
| strcat(proto, "//"); |
| ptr += 5; |
| } |
| } |
| |
| if (!strncmp(ptr, "//", 2)) { |
| const char *s = ptr + 2; |
| const char *q = NULL; |
| |
| /* Isolate the server(s). */ |
| if ((q = strchr(s, '/')) || (q = strchr(s, '\0'))) { |
| l = q - s; |
| if (*proto) { |
| al_len = l + strlen(proto) + 2; |
| tmp = malloc(al_len); |
| } else { |
| al_len = l + 1; |
| tmp = malloc(al_len); |
| } |
| if (!tmp) { |
| char *estr; |
| estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "malloc: %s", estr); |
| return 0; |
| } |
| ctxt->server = tmp; |
| memset(ctxt->server, 0, al_len); |
| if (*proto) { |
| strcpy(ctxt->server, proto); |
| memcpy(ctxt->server + strlen(proto), s, l); |
| strcat(ctxt->server, "/"); |
| } else |
| memcpy(ctxt->server, s, l); |
| ptr = q + 1; |
| } else { |
| crit(logopt, |
| MODPREFIX "invalid LDAP map syntax %s", ptr); |
| return 0; |
| /* TODO: why did I put this here, the parser shouldn't let this by |
| l = strlen(ptr); |
| tmp = malloc(l + 1); |
| if (!tmp) { |
| char *estr; |
| estr = strerror_r(errno, buf, sizeof(buf)); |
| crit(logopt, MODPREFIX "malloc: %s", estr); |
| return 0; |
| } |
| ctxt->server = tmp; |
| memset(ctxt->server, 0, l + 1); |
| memcpy(ctxt->server, s, l); |
| */ |
| } |
| } else if (strchr(ptr, ':') != NULL || *ptr == '[') { |
| const char *q = NULL; |
| |
| /* Isolate the server. Include the port spec */ |
| if (*ptr != '[') { |
| q = strchr(ptr, ':'); |
| if (!q) { |
| crit(logopt, MODPREFIX |
| "LDAP server name not found in %s", ptr); |
| return 0; |
| } |
| } else { |
| q = ++ptr; |
| while (*q == ':' || isxdigit(*q)) |
| q++; |
| if (*q != ']') { |
| crit(logopt, MODPREFIX |
| "invalid LDAP map syntax %s", ptr); |
| return 0; |
| } |
| q++; |
| if (*q == ':') |
| q++; |
| } |
| |
| if (isdigit(*q)) |
| while (isdigit(*q)) |
| q++; |
| |
| if (*q != ':') { |
| crit(logopt, |
| MODPREFIX "invalid LDAP map syntax %s", ptr); |
| return 0; |
| } |
| |
| l = q - ptr; |
| if (*proto) { |
| al_len = l + strlen(proto) + 2; |
| tmp = malloc(al_len); |
| } else { |
| al_len = l + 1; |
| tmp = malloc(al_len); |
| } |
| /* Isolate the server's name. */ |
| if (!tmp) { |
| char *estr; |
| estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "malloc: %s", estr); |
| return 0; |
| } |
| ctxt->server = tmp; |
| memset(ctxt->server, 0, al_len); |
| if (*proto) { |
| strcpy(ctxt->server, proto); |
| memcpy(ctxt->server + strlen(proto), ptr, l); |
| strcat(ctxt->server, "/"); |
| } else |
| memcpy(ctxt->server, ptr, l); |
| ptr += l + 1; |
| } |
| |
| if (!ptr || ctxt->format & MAP_FLAG_FORMAT_AMD) |
| goto done; |
| |
| /* |
| * For nss support we can have a map name with no |
| * type or dn info. If present a base dn must have |
| * at least an "=" and a "," to be at all functional. |
| * If a dn is given it must be fully specified or |
| * the later LDAP calls will fail. |
| */ |
| l = strlen(ptr); |
| if ((name = strchr(ptr, '='))) { |
| char *base; |
| |
| /* |
| * An '=' with no ',' means a mapname has been given so just |
| * grab it alone to keep it independent of schema otherwize |
| * we expect a full dn. |
| */ |
| if (!strchr(ptr, ',')) { |
| char *map = strdup(name + 1); |
| if (map) |
| ctxt->mapname = map; |
| else { |
| char *estr; |
| estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "strdup: %s", estr); |
| if (ctxt->server) |
| free(ctxt->server); |
| return 0; |
| } |
| |
| } else { |
| base = malloc(l + 1); |
| if (!base) { |
| char *estr; |
| estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "malloc: %s", estr); |
| if (ctxt->server) |
| free(ctxt->server); |
| return 0; |
| } |
| ctxt->base = base; |
| memset(ctxt->base, 0, l + 1); |
| memcpy(ctxt->base, ptr, l); |
| } |
| } else { |
| char *map = malloc(l + 1); |
| if (!map) { |
| char *estr; |
| estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "malloc: %s", estr); |
| if (ctxt->server) |
| free(ctxt->server); |
| return 0; |
| } |
| ctxt->mapname = map; |
| memset(ctxt->mapname, 0, l + 1); |
| memcpy(map, ptr, l); |
| } |
| |
| if (!ctxt->server && *proto) { |
| if (!strncmp(proto, "ldaps", 5)) { |
| info(logopt, MODPREFIX |
| "server must be given to force ldaps, connection " |
| "will use LDAP client configured protocol"); |
| } |
| } |
| done: |
| if (ctxt->mapname) |
| debug(logopt, MODPREFIX "mapname %s", ctxt->mapname); |
| else |
| debug(logopt, MODPREFIX "server \"%s\", base dn \"%s\"", |
| ctxt->server ? ctxt->server : "(default)", |
| ctxt->base); |
| |
| return 1; |
| } |
| |
| static void free_context(struct lookup_context *ctxt) |
| { |
| int ret; |
| |
| if (ctxt->schema) { |
| free(ctxt->schema->map_class); |
| free(ctxt->schema->map_attr); |
| free(ctxt->schema->entry_class); |
| free(ctxt->schema->entry_attr); |
| free(ctxt->schema->value_attr); |
| free(ctxt->schema); |
| } |
| if (ctxt->auth_conf) |
| free(ctxt->auth_conf); |
| if (ctxt->sasl_mech) |
| free(ctxt->sasl_mech); |
| if (ctxt->user) |
| free(ctxt->user); |
| if (ctxt->secret) |
| free(ctxt->secret); |
| if (ctxt->client_princ) |
| free(ctxt->client_princ); |
| if (ctxt->client_cc) |
| free(ctxt->client_cc); |
| if (ctxt->mapname) |
| free(ctxt->mapname); |
| if (ctxt->qdn) |
| free(ctxt->qdn); |
| if (ctxt->server) |
| free(ctxt->server); |
| if (ctxt->cur_host) |
| free(ctxt->cur_host); |
| if (ctxt->base) |
| free(ctxt->base); |
| if (ctxt->uris) |
| defaults_free_uris(ctxt->uris); |
| ret = pthread_mutex_destroy(&ctxt->uris_mutex); |
| if (ret) |
| fatal(ret); |
| if (ctxt->sdns) |
| defaults_free_searchdns(ctxt->sdns); |
| if (ctxt->dclist) |
| free_dclist(ctxt->dclist); |
| #ifdef WITH_SASL |
| if (ctxt->extern_cert) |
| free(ctxt->extern_cert); |
| if (ctxt->extern_key) |
| free(ctxt->extern_key); |
| #endif |
| free(ctxt); |
| |
| return; |
| } |
| |
| static void validate_uris(struct list_head *list) |
| { |
| struct list_head *next; |
| |
| next = list->next; |
| while (next != list) { |
| struct ldap_uri *this; |
| |
| this = list_entry(next, struct ldap_uri, list); |
| next = next->next; |
| |
| /* At least we get some basic validation */ |
| if (!ldap_is_ldap_url(this->uri)) { |
| list_del(&this->list); |
| free(this->uri); |
| free(this); |
| } |
| } |
| |
| return; |
| } |
| |
| static int do_init(const char *mapfmt, |
| int argc, const char *const *argv, |
| struct lookup_context *ctxt, unsigned int reinit) |
| { |
| unsigned int is_amd_format; |
| int ret; |
| |
| ret = pthread_mutex_init(&ctxt->uris_mutex, NULL); |
| if (ret) { |
| error(LOGOPT_ANY, MODPREFIX "failed to init uris mutex"); |
| return 1; |
| } |
| |
| /* If a map type isn't explicitly given, parse it like sun entries. */ |
| if (mapfmt == NULL) |
| mapfmt = MAPFMT_DEFAULT; |
| |
| is_amd_format = 0; |
| if (!strcmp(mapfmt, "amd")) { |
| is_amd_format = 1; |
| ctxt->format = MAP_FLAG_FORMAT_AMD; |
| ctxt->check_defaults = 1; |
| } |
| |
| ctxt->timeout = defaults_get_ldap_timeout(); |
| ctxt->network_timeout = defaults_get_ldap_network_timeout(); |
| |
| if (!is_amd_format) { |
| /* |
| * Parse out the server name and base dn, and fill them |
| * into the proper places in the lookup context structure. |
| */ |
| if (!parse_server_string(LOGOPT_NONE, argv[0], ctxt)) { |
| error(LOGOPT_ANY, MODPREFIX "cannot parse server string"); |
| return 1; |
| } |
| |
| if (!ctxt->base) |
| ctxt->sdns = defaults_get_searchdns(); |
| |
| if (!ctxt->server) { |
| struct list_head *uris = defaults_get_uris(); |
| if (uris) { |
| validate_uris(uris); |
| if (!list_empty(uris)) |
| ctxt->uris = uris; |
| else { |
| error(LOGOPT_ANY, MODPREFIX |
| "no valid uris found in config list" |
| ", using default system config"); |
| free(uris); |
| } |
| } |
| } |
| } else { |
| char *tmp = conf_amd_get_ldap_base(); |
| if (!tmp) { |
| error(LOGOPT_ANY, MODPREFIX "failed to get base dn"); |
| return 1; |
| } |
| ctxt->base = tmp; |
| |
| tmp = conf_amd_get_ldap_hostports(); |
| if (!tmp) { |
| error(LOGOPT_ANY, |
| MODPREFIX "failed to get ldap_hostports"); |
| return 1; |
| } |
| |
| /* |
| * Parse out the server name and port, and save them in |
| * the proper places in the lookup context structure. |
| */ |
| if (!parse_server_string(LOGOPT_NONE, tmp, ctxt)) { |
| error(LOGOPT_ANY, MODPREFIX "cannot parse server string"); |
| free(tmp); |
| return 1; |
| } |
| free(tmp); |
| |
| if (!ctxt->server) { |
| error(LOGOPT_ANY, MODPREFIX "ldap_hostports not valid"); |
| return 1; |
| } |
| |
| tmp = strdup(argv[0]); |
| if (!tmp) { |
| error(LOGOPT_ANY, MODPREFIX "failed to set mapname"); |
| return 1; |
| } |
| ctxt->mapname = tmp; |
| } |
| |
| /* |
| * First, check to see if a preferred authentication method was |
| * specified by the user. parse_ldap_config will return error |
| * if the permissions on the file were incorrect, or if the |
| * specified authentication type is not valid. |
| */ |
| ret = parse_ldap_config(LOGOPT_NONE, ctxt); |
| if (ret) { |
| error(LOGOPT_ANY, MODPREFIX "failed to parse ldap config"); |
| return 1; |
| } |
| |
| #ifdef WITH_SASL |
| /* Init the sasl callbacks */ |
| ldapinit_mutex_lock(); |
| if (!autofs_sasl_client_init(LOGOPT_NONE)) { |
| error(LOGOPT_ANY, "failed to init sasl client"); |
| ldapinit_mutex_unlock(); |
| return 1; |
| } |
| ldapinit_mutex_unlock(); |
| #endif |
| |
| if (is_amd_format) |
| ctxt->timestamp = get_amd_timestamp(ctxt); |
| |
| if (reinit) { |
| ret = reinit_parse(ctxt->parse, |
| mapfmt, MODPREFIX, argc - 1, argv + 1); |
| if (ret) |
| logmsg(MODPREFIX "failed to reinit parse context"); |
| } else { |
| /* Open the parser, if we can. */ |
| ctxt->parse = open_parse(mapfmt, MODPREFIX, argc - 1, argv + 1); |
| if (!ctxt->parse) { |
| logerr(MODPREFIX "failed to open parse context"); |
| ret = 1; |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * This initializes a context (persistent non-global data) for queries to |
| * this module. Return zero if we succeed. |
| */ |
| int lookup_init(const char *mapfmt, |
| int argc, const char *const *argv, void **context) |
| { |
| struct lookup_context *ctxt; |
| char buf[MAX_ERR_BUF]; |
| int ret; |
| |
| *context = NULL; |
| |
| /* If we can't build a context, bail. */ |
| ctxt = malloc(sizeof(struct lookup_context)); |
| if (!ctxt) { |
| char *estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "malloc: %s", estr); |
| return 1; |
| } |
| memset(ctxt, 0, sizeof(struct lookup_context)); |
| |
| ret = do_init(mapfmt, argc, argv, ctxt, 0); |
| if (ret) { |
| free_context(ctxt); |
| return 1; |
| } |
| |
| *context = ctxt; |
| |
| return 0; |
| } |
| |
| int lookup_reinit(const char *mapfmt, |
| int argc, const char *const *argv, void **context) |
| { |
| struct lookup_context *ctxt = (struct lookup_context *) *context; |
| struct lookup_context *new; |
| char buf[MAX_ERR_BUF]; |
| int ret; |
| |
| /* If we can't build a context, bail. */ |
| new = malloc(sizeof(struct lookup_context)); |
| if (!new) { |
| char *estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "malloc: %s", estr); |
| return 1; |
| } |
| memset(new, 0, sizeof(struct lookup_context)); |
| |
| new->parse = ctxt->parse; |
| ret = do_init(mapfmt, argc, argv, new, 1); |
| if (ret) { |
| free_context(new); |
| return 1; |
| } |
| |
| *context = new; |
| |
| free_context(ctxt); |
| |
| return 0; |
| } |
| |
| int lookup_read_master(struct master *master, time_t age, void *context) |
| { |
| struct lookup_context *ctxt = (struct lookup_context *) context; |
| unsigned int timeout = master->default_timeout; |
| unsigned int logging = master->default_logging; |
| unsigned int logopt = master->logopt; |
| struct ldap_conn conn; |
| LDAP *ldap; |
| int rv, l, count; |
| char buf[MAX_ERR_BUF]; |
| char parse_buf[PARSE_MAX_BUF]; |
| char *query; |
| LDAPMessage *result = NULL, *e; |
| char *class, *info, *entry; |
| char **keyValue = NULL; |
| char **values = NULL; |
| char *attrs[3]; |
| int scope = LDAP_SCOPE_SUBTREE; |
| |
| /* Initialize the LDAP context. */ |
| memset(&conn, 0, sizeof(struct ldap_conn)); |
| rv = do_reconnect(logopt, &conn, ctxt); |
| if (rv) |
| return rv; |
| ldap = conn.ldap; |
| |
| class = ctxt->schema->entry_class; |
| entry = ctxt->schema->entry_attr; |
| info = ctxt->schema->value_attr; |
| |
| attrs[0] = entry; |
| attrs[1] = info; |
| attrs[2] = NULL; |
| |
| l = strlen("(objectclass=)") + strlen(class) + 1; |
| |
| query = malloc(l); |
| if (query == NULL) { |
| char *estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "malloc: %s", estr); |
| return NSS_STATUS_UNAVAIL; |
| } |
| |
| if (sprintf(query, "(objectclass=%s)", class) >= l) { |
| error(logopt, MODPREFIX "error forming query string"); |
| free(query); |
| return NSS_STATUS_UNAVAIL; |
| } |
| |
| /* Look around. */ |
| debug(logopt, |
| MODPREFIX "searching for \"%s\" under \"%s\"", query, ctxt->qdn); |
| |
| rv = ldap_search_s(ldap, ctxt->qdn, scope, query, attrs, 0, &result); |
| |
| if ((rv != LDAP_SUCCESS) || !result) { |
| error(logopt, MODPREFIX "query failed for %s: %s", |
| query, ldap_err2string(rv)); |
| unbind_ldap_connection(logging, &conn, ctxt); |
| if (result) |
| ldap_msgfree(result); |
| free(query); |
| return NSS_STATUS_NOTFOUND; |
| } |
| |
| e = ldap_first_entry(ldap, result); |
| if (!e) { |
| debug(logopt, |
| MODPREFIX "query succeeded, no matches for %s", |
| query); |
| ldap_msgfree(result); |
| unbind_ldap_connection(logging, &conn, ctxt); |
| free(query); |
| return NSS_STATUS_NOTFOUND; |
| } else |
| debug(logopt, MODPREFIX "examining entries"); |
| |
| while (e) { |
| char *key = NULL; |
| int dec_len, i; |
| |
| keyValue = ldap_get_values(ldap, e, entry); |
| |
| if (!keyValue || !*keyValue) { |
| e = ldap_next_entry(ldap, e); |
| continue; |
| } |
| |
| /* |
| * By definition keys must be unique within |
| * each map entry |
| */ |
| count = ldap_count_values(keyValue); |
| if (strcasecmp(class, "nisObject")) { |
| if (count > 1) { |
| error(logopt, MODPREFIX |
| "key %s has duplicates - ignoring", |
| *keyValue); |
| goto next; |
| } |
| key = strdup(keyValue[0]); |
| if (!key) { |
| error(logopt, MODPREFIX |
| "failed to dup map key %s - ignoring", |
| *keyValue); |
| goto next; |
| } |
| } else if (count == 1) { |
| dec_len = decode_percent_hack(keyValue[0], &key); |
| if (dec_len < 0) { |
| error(logopt, MODPREFIX |
| "invalid map key %s - ignoring", |
| *keyValue); |
| goto next; |
| } |
| } else { |
| dec_len = decode_percent_hack(keyValue[0], &key); |
| if (dec_len < 0) { |
| error(logopt, MODPREFIX |
| "invalid map key %s - ignoring", |
| *keyValue); |
| goto next; |
| } |
| |
| for (i = 1; i < count; i++) { |
| char *k; |
| dec_len = decode_percent_hack(keyValue[i], &k); |
| if (dec_len < 0) { |
| error(logopt, MODPREFIX |
| "invalid map key %s - ignoring", |
| *keyValue); |
| goto next; |
| } |
| if (strcmp(key, k)) { |
| error(logopt, MODPREFIX |
| "key entry mismatch %s - ignoring", |
| *keyValue); |
| free(k); |
| goto next; |
| } |
| free(k); |
| } |
| } |
| |
| /* |
| * Ignore keys beginning with '+' as plus map |
| * inclusion is only valid in file maps. |
| */ |
| if (*key == '+') { |
| warn(logopt, |
| MODPREFIX |
| "ignoreing '+' map entry - not in file map"); |
| goto next; |
| } |
| |
| values = ldap_get_values(ldap, e, info); |
| if (!values || !*values) { |
| debug(logopt, |
| MODPREFIX "no %s defined for %s", info, query); |
| goto next; |
| } |
| |
| /* |
| * We require that there be only one value per key. |
| */ |
| count = ldap_count_values(values); |
| if (count > 1) { |
| error(logopt, |
| MODPREFIX |
| "one value per key allowed in master map"); |
| ldap_value_free(values); |
| goto next; |
| } |
| |
| if (snprintf(parse_buf, sizeof(parse_buf), "%s %s", |
| key, *values) >= sizeof(parse_buf)) { |
| error(logopt, MODPREFIX "map entry too long"); |
| ldap_value_free(values); |
| goto next; |
| } |
| ldap_value_free(values); |
| |
| master_parse_entry(parse_buf, timeout, logging, age); |
| next: |
| ldap_value_free(keyValue); |
| if (key) |
| free(key); |
| e = ldap_next_entry(ldap, e); |
| } |
| |
| /* Clean up. */ |
| ldap_msgfree(result); |
| unbind_ldap_connection(logopt, &conn, ctxt); |
| free(query); |
| |
| return NSS_STATUS_SUCCESS; |
| } |
| |
| static int get_percent_decoded_len(const char *name) |
| { |
| int escapes = 0; |
| int escaped = 0; |
| const char *tmp = name; |
| int look_for_close = 0; |
| |
| while (*tmp) { |
| if (*tmp == '%') { |
| /* assume escapes aren't interpreted inside brackets */ |
| if (look_for_close) { |
| tmp++; |
| continue; |
| } |
| /* check for escaped % */ |
| if (escaped) { |
| tmp++; |
| escaped = 0; |
| continue; |
| } |
| escapes++; |
| tmp++; |
| if (*tmp == '[') { |
| escapes++; |
| tmp++; |
| look_for_close = 1; |
| } else |
| escaped = 1; |
| } else if (*tmp == ']' && look_for_close) { |
| escaped = 0; |
| escapes++; |
| tmp++; |
| look_for_close = 0; |
| } else { |
| tmp++; |
| escaped = 0; |
| } |
| } |
| |
| assert(strlen(name) > escapes); |
| return strlen(name) - escapes; |
| } |
| |
| /* |
| * Try to catch heap corruption if our logic happens to be incorrect. |
| */ |
| static void validate_string_len(const char *orig, char *start, |
| char *end, unsigned int len) |
| { |
| debug(LOGOPT_NONE, MODPREFIX "string %s encoded as %s", orig, start); |
| /* make sure we didn't overflow the allocated space */ |
| if (end - start > len + 1) { |
| crit(LOGOPT_ANY, MODPREFIX "orig %s, len %d", orig, len); |
| crit(LOGOPT_ANY, MODPREFIX "en/decoded %s, len %d", start, |
| end - start); |
| } |
| assert(end-start <= len + 1); |
| } |
| |
| /* |
| * Deal with encode and decode of % hack. |
| * Return |
| * 0 => % hack not present. |
| * -1 => syntax error or alloc fail. |
| * 1 transofrmed value returned. |
| */ |
| /* |
| * Assumptions: %'s must be escaped by %'s. %'s are not used to escape |
| * anything else except capital letters (so you can't escape a closing |
| * bracket, for example). |
| */ |
| static int decode_percent_hack(const char *name, char **key) |
| { |
| const char *tmp; |
| char *ptr, *new; |
| unsigned int len; |
| int escaped = 0, look_for_close = 0; |
| |
| if (!key) |
| return -1; |
| |
| *key = NULL; |
| |
| len = get_percent_decoded_len(name); |
| new = malloc(len + 1); |
| if (!new) |
| return -1; |
| |
| ptr = new; |
| tmp = name; |
| while (*tmp) { |
| if (*tmp == '%') { |
| if (escaped) { |
| *ptr++ = *tmp++; |
| if (!look_for_close) |
| escaped = 0; |
| continue; |
| } |
| tmp++; |
| if (*tmp == '[') { |
| tmp++; |
| look_for_close = 1; |
| escaped = 1; |
| } else |
| escaped = 1; |
| } else if (*tmp == ']' && look_for_close) { |
| tmp++; |
| look_for_close = 0; |
| } else { |
| escaped = 0; |
| *ptr++ = *tmp++; |
| } |
| } |
| *ptr = '\0'; |
| *key = new; |
| |
| validate_string_len(name, new, ptr, len); |
| return strlen(new); |
| } |
| |
| /* |
| * Calculate the length of a string replacing all capital letters with %letter. |
| * For example: |
| * Sale -> %Sale |
| * SALE -> %S%A%L%E |
| */ |
| static int get_encoded_len_escaping_every_cap(const char *name) |
| { |
| const char *tmp; |
| unsigned int escapes = 0; /* number of % escape characters */ |
| |
| tmp = name; |
| while (*tmp) { |
| /* We'll need to escape percents */ |
| if (*tmp == '%' || isupper(*tmp)) |
| escapes++; |
| tmp++; |
| } |
| |
| return strlen(name) + escapes; |
| } |
| |
| /* |
| * Calculate the length of a string replacing sequences (1 or more) of capital |
| * letters with %[letters]. For example: |
| * FOO -> %[FOO] |
| * Work -> %[W]ork |
| * WorksForMe -> %[W]orks%[F]or%[M]e |
| * aaBBaa -> aa%[BB]aa |
| */ |
| static int get_encoded_len_escaping_sequences(const char *name) |
| { |
| const char *tmp; |
| unsigned int escapes = 0; |
| |
| tmp = name; |
| while (*tmp) { |
| /* escape percents */ |
| if (*tmp == '%') |
| escapes++; |
| else if (isupper(*tmp)) { |
| /* start an escape block %[...] */ |
| escapes += 3; /* %[] */ |
| while (*tmp && isupper(*tmp)) |
| tmp++; |
| continue; |
| } |
| tmp++; |
| } |
| |
| return strlen(name) + escapes; |
| } |
| |
| static void encode_individual(const char *name, char *new, unsigned int len) |
| { |
| const char *tmp; |
| char *ptr; |
| |
| ptr = new; |
| tmp = name; |
| while (*tmp) { |
| if (*tmp == '%' || isupper(*tmp)) |
| *ptr++ = '%'; |
| *ptr++ = *tmp++; |
| } |
| *ptr = '\0'; |
| validate_string_len(name, new, ptr, len); |
| } |
| |
| static void encode_sequence(const char *name, char *new, unsigned int len) |
| { |
| const char *tmp; |
| char *ptr; |
| |
| ptr = new; |
| tmp = name; |
| while (*tmp) { |
| if (*tmp == '%') { |
| *ptr++ = '%'; |
| *ptr++ = *tmp++; |
| } else if (isupper(*tmp)) { |
| *ptr++ = '%'; |
| *ptr++ = '['; |
| *ptr++ = *tmp++; |
| |
| while (*tmp && isupper(*tmp)) { |
| *ptr++ = *tmp; |
| tmp++; |
| } |
| *ptr++ = ']'; |
| } else |
| *ptr++ = *tmp++; |
| } |
| *ptr = '\0'; |
| validate_string_len(name, new, ptr, len); |
| } |
| |
| /* |
| * use_class: 1 means encode string as %[CAPITALS], 0 means encode as |
| * %C%A%P%I%T%A%L%S |
| */ |
| static int encode_percent_hack(const char *name, char **key, unsigned int use_class) |
| { |
| unsigned int len = 0; |
| |
| if (!key) |
| return -1; |
| |
| if (use_class) |
| len = get_encoded_len_escaping_sequences(name); |
| else |
| len = get_encoded_len_escaping_every_cap(name); |
| |
| /* If there is no escaping to be done, return 0 */ |
| if (len == strlen(name)) |
| return 0; |
| |
| *key = malloc(len + 1); |
| if (!*key) |
| return -1; |
| |
| if (use_class) |
| encode_sequence(name, *key, len); |
| else |
| encode_individual(name, *key, len); |
| |
| if (strlen(*key) != len) |
| crit(LOGOPT_ANY, MODPREFIX "encoded key length mismatch: key " |
| "%s len %d strlen %d", *key, len, strlen(*key)); |
| |
| return strlen(*key); |
| } |
| |
| static int do_paged_query(struct ldap_search_params *sp, struct lookup_context *ctxt) |
| { |
| struct autofs_point *ap = sp->ap; |
| LDAPControl *pageControl=NULL, *controls[2] = { NULL, NULL }; |
| LDAPControl **returnedControls = NULL; |
| static char pagingCriticality = 'T'; |
| int rv, scope = LDAP_SCOPE_SUBTREE; |
| |
| if (sp->morePages == TRUE) |
| goto do_paged; |
| |
| rv = ldap_search_s(sp->ldap, sp->base, scope, sp->query, sp->attrs, 0, &sp->result); |
| if ((rv != LDAP_SUCCESS) || !sp->result) { |
| /* |
| * Check for Size Limit exceeded and force run through loop |
| * and requery using page control. |
| */ |
| if (rv == LDAP_SIZELIMIT_EXCEEDED || |
| rv == LDAP_ADMINLIMIT_EXCEEDED) |
| sp->morePages = TRUE; |
| else { |
| debug(ap->logopt, |
| MODPREFIX "query failed for %s: %s", |
| sp->query, ldap_err2string(rv)); |
| return rv; |
| } |
| } |
| return rv; |
| |
| do_paged: |
| /* we need to use page controls so requery LDAP */ |
| debug(ap->logopt, MODPREFIX "geting page of results"); |
| |
| rv = ldap_create_page_control(sp->ldap, sp->pageSize, sp->cookie, |
| pagingCriticality, &pageControl); |
| if (rv != LDAP_SUCCESS) { |
| warn(ap->logopt, MODPREFIX "failed to create page control"); |
| return rv; |
| } |
| |
| /* Insert the control into a list to be passed to the search. */ |
| controls[0] = pageControl; |
| |
| /* Search for entries in the directory using the parmeters. */ |
| rv = ldap_search_ext_s(sp->ldap, |
| sp->base, scope, sp->query, sp->attrs, |
| 0, controls, NULL, NULL, 0, &sp->result); |
| if ((rv != LDAP_SUCCESS) && (rv != LDAP_PARTIAL_RESULTS)) { |
| ldap_control_free(pageControl); |
| if (rv != LDAP_ADMINLIMIT_EXCEEDED) |
| debug(ap->logopt, |
| MODPREFIX "query failed for %s: %s", |
| sp->query, ldap_err2string(rv)); |
| return rv; |
| } |
| |
| /* Parse the results to retrieve the contols being returned. */ |
| rv = ldap_parse_result(sp->ldap, sp->result, |
| NULL, NULL, NULL, NULL, |
| &returnedControls, FALSE); |
| if (sp->cookie != NULL) { |
| ber_bvfree(sp->cookie); |
| sp->cookie = NULL; |
| } |
| |
| if (rv != LDAP_SUCCESS) { |
| debug(ap->logopt, |
| MODPREFIX "ldap_parse_result failed with %d", rv); |
| goto out_free; |
| } |
| |
| /* |
| * Parse the page control returned to get the cookie and |
| * determine whether there are more pages. |
| */ |
| rv = ldap_parse_page_control(sp->ldap, |
| returnedControls, &sp->totalCount, |
| &sp->cookie); |
| if (sp->cookie && sp->cookie->bv_val && |
| (strlen(sp->cookie->bv_val) || sp->cookie->bv_len)) |
| sp->morePages = TRUE; |
| else |
| sp->morePages = FALSE; |
| |
| /* Cleanup the controls used. */ |
| if (returnedControls) |
| ldap_controls_free(returnedControls); |
| |
| out_free: |
| ldap_control_free(pageControl); |
| return rv; |
| } |
| |
| static int do_get_entries(struct ldap_search_params *sp, struct map_source *source, struct lookup_context *ctxt) |
| { |
| struct autofs_point *ap = sp->ap; |
| struct mapent_cache *mc = source->mc; |
| char buf[MAX_ERR_BUF]; |
| struct berval **bvKey; |
| struct berval **bvValues; |
| LDAPMessage *e; |
| char *class, *info, *entry; |
| int rv, ret; |
| int i, count; |
| |
| class = ctxt->schema->entry_class; |
| entry = ctxt->schema->entry_attr; |
| info = ctxt->schema->value_attr; |
| |
| e = ldap_first_entry(sp->ldap, sp->result); |
| if (!e) { |
| debug(ap->logopt, |
| MODPREFIX "query succeeded, no matches for %s", |
| sp->query); |
| ret = ldap_parse_result(sp->ldap, sp->result, |
| &rv, NULL, NULL, NULL, NULL, 0); |
| if (ret == LDAP_SUCCESS) |
| return rv; |
| else |
| return LDAP_OPERATIONS_ERROR; |
| } else |
| debug(ap->logopt, MODPREFIX "examining entries"); |
| |
| while (e) { |
| char *mapent = NULL; |
| size_t mapent_len = 0; |
| char *k_val; |
| ber_len_t k_len; |
| char *s_key; |
| |
| bvKey = ldap_get_values_len(sp->ldap, e, entry); |
| if (!bvKey || !*bvKey) { |
| e = ldap_next_entry(sp->ldap, e); |
| if (!e) { |
| debug(ap->logopt, MODPREFIX |
| "failed to get next entry for query %s", |
| sp->query); |
| ret = ldap_parse_result(sp->ldap, |
| sp->result, &rv, |
| NULL, NULL, NULL, NULL, 0); |
| if (ret == LDAP_SUCCESS) |
| return rv; |
| else |
| return LDAP_OPERATIONS_ERROR; |
| } |
| continue; |
| } |
| |
| /* |
| * By definition keys should be unique within each map entry, |
| * but as always there are exceptions. |
| */ |
| k_val = NULL; |
| k_len = 0; |
| |
| /* |
| * Keys should be unique so, in general, there shouldn't be |
| * more than one attribute value. We make an exception for |
| * wildcard entries as people may have values for '*' or |
| * '/' for compaibility reasons. We use the '/' as the |
| * wildcard in LDAP but allow '*' as well to allow for |
| * people using older schemas that allow '*' as a key |
| * value. Another case where there can be multiple key |
| * values is when people have used the "%" hack to specify |
| * case matching ctriteria in a case insensitive attribute. |
| */ |
| count = ldap_count_values_len(bvKey); |
| if (count > 1) { |
| unsigned int i; |
| |
| /* Check for the "/" and "*" and use as "/" if found */ |
| for (i = 0; i < count; i++) { |
| bvKey[i]->bv_val[bvKey[i]->bv_len] = '\0'; |
| |
| /* |
| * If multiple entries are present they could |
| * be the result of people using the "%" hack so |
| * ignore them. |
| */ |
| if (strchr(bvKey[i]->bv_val, '%')) |
| continue; |
| |
| /* check for wildcard */ |
| if (bvKey[i]->bv_len == 1 && |
| (*bvKey[i]->bv_val == '/' || |
| *bvKey[i]->bv_val == '*')) { |
| /* always use '/' internally */ |
| *bvKey[i]->bv_val = '/'; |
| k_val = bvKey[i]->bv_val; |
| k_len = 1; |
| break; |
| } |
| |
| /* |
| * We have a result from LDAP so this is a |
| * valid entry. Set the result to the LDAP |
| * key that isn't a wildcard and doesn't have |
| * any "%" hack values present. This should be |
| * the case insensitive match string for the |
| * nis schema, the default value. |
| */ |
| k_val = bvKey[i]->bv_val; |
| k_len = bvKey[i]->bv_len; |
| |
| break; |
| } |
| |
| if (!k_val) { |
| error(ap->logopt, |
| MODPREFIX "invalid entry %.*s - ignoring", |
| bvKey[0]->bv_len, bvKey[0]->bv_val); |
| goto next; |
| } |
| } else { |
| /* Check for the "*" and use as "/" if found */ |
| if (bvKey[0]->bv_len == 1 && *bvKey[0]->bv_val == '*') |
| *bvKey[0]->bv_val = '/'; |
| k_val = bvKey[0]->bv_val; |
| k_len = bvKey[0]->bv_len; |
| } |
| |
| /* |
| * Ignore keys beginning with '+' as plus map |
| * inclusion is only valid in file maps. |
| */ |
| if (*k_val == '+') { |
| warn(ap->logopt, |
| MODPREFIX |
| "ignoreing '+' map entry - not in file map"); |
| goto next; |
| } |
| |
| bvValues = ldap_get_values_len(sp->ldap, e, info); |
| if (!bvValues || !*bvValues) { |
| debug(ap->logopt, |
| MODPREFIX "no %s defined for %s", info, sp->query); |
| goto next; |
| } |
| |
| /* |
| * We expect that there will be only one value because |
| * questions of order of returned value entries but we |
| * accumulate values to support simple multi-mounts. |
| * |
| * If the ordering of a mount spec with another containing |
| * options or the actual order of entries causes problems |
| * it won't be supported. Perhaps someone can instruct us |
| * how to force an ordering. |
| */ |
| count = ldap_count_values_len(bvValues); |
| for (i = 0; i < count; i++) { |
| char *v_val = bvValues[i]->bv_val; |
| ber_len_t v_len = bvValues[i]->bv_len; |
| |
| if (!mapent) { |
| mapent = malloc(v_len + 1); |
| if (!mapent) { |
| char *estr; |
| estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "malloc: %s", estr); |
| ldap_value_free_len(bvValues); |
| goto next; |
| } |
| strncpy(mapent, v_val, v_len); |
| mapent[v_len] = '\0'; |
| mapent_len = v_len; |
| } else { |
| int new_size = mapent_len + v_len + 2; |
| char *new_me; |
| new_me = realloc(mapent, new_size); |
| if (new_me) { |
| mapent = new_me; |
| strcat(mapent, " "); |
| strncat(mapent, v_val, v_len); |
| mapent[new_size - 1] = '\0'; |
| mapent_len = new_size - 1; |
| } else { |
| char *estr; |
| estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "realloc: %s", estr); |
| } |
| } |
| } |
| ldap_value_free_len(bvValues); |
| |
| if (*k_val == '/' && k_len == 1) { |
| if (ap->type == LKP_DIRECT) |
| goto next; |
| *k_val = '*'; |
| } |
| |
| if (strcasecmp(class, "nisObject")) { |
| s_key = sanitize_path(k_val, k_len, ap->type, ap->logopt); |
| if (!s_key) |
| goto next; |
| } else { |
| char *dec_key; |
| int dec_len = decode_percent_hack(k_val, &dec_key); |
| |
| if (dec_len < 0) { |
| crit(ap->logopt, |
| "could not use percent hack to decode key %s", |
| k_val); |
| goto next; |
| } |
| |
| if (dec_len == 0) |
| s_key = sanitize_path(k_val, k_len, ap->type, ap->logopt); |
| else { |
| s_key = sanitize_path(dec_key, dec_len, ap->type, ap->logopt); |
| free(dec_key); |
| } |
| if (!s_key) |
| goto next; |
| } |
| |
| cache_writelock(mc); |
| cache_update(mc, source, s_key, mapent, sp->age); |
| cache_unlock(mc); |
| |
| free(s_key); |
| next: |
| if (mapent) { |
| free(mapent); |
| mapent = NULL; |
| } |
| |
| ldap_value_free_len(bvKey); |
| e = ldap_next_entry(sp->ldap, e); |
| if (!e) { |
| debug(ap->logopt, MODPREFIX |
| "failed to get next entry for query %s", |
| sp->query); |
| ret = ldap_parse_result(sp->ldap, |
| sp->result, &rv, |
| NULL, NULL, NULL, NULL, 0); |
| if (ret == LDAP_SUCCESS) |
| return rv; |
| else |
| return LDAP_OPERATIONS_ERROR; |
| } |
| } |
| |
| return LDAP_SUCCESS; |
| } |
| |
| static int do_get_amd_entries(struct ldap_search_params *sp, |
| struct map_source *source, |
| struct lookup_context *ctxt) |
| { |
| struct autofs_point *ap = sp->ap; |
| struct mapent_cache *mc = source->mc; |
| struct berval **bvKey; |
| struct berval **bvValues; |
| LDAPMessage *e; |
| char *entry, *value; |
| int rv, ret, count; |
| |
| entry = ctxt->schema->entry_attr; |
| value = ctxt->schema->value_attr; |
| |
| e = ldap_first_entry(sp->ldap, sp->result); |
| if (!e) { |
| debug(ap->logopt, |
| MODPREFIX "query succeeded, no matches for %s", |
| sp->query); |
| ret = ldap_parse_result(sp->ldap, sp->result, |
| &rv, NULL, NULL, NULL, NULL, 0); |
| if (ret == LDAP_SUCCESS) |
| return rv; |
| else |
| return LDAP_OPERATIONS_ERROR; |
| } else |
| debug(ap->logopt, MODPREFIX "examining entries"); |
| |
| while (e) { |
| char *k_val, *v_val; |
| ber_len_t k_len; |
| char *s_key; |
| |
| bvKey = ldap_get_values_len(sp->ldap, e, entry); |
| if (!bvKey || !*bvKey) { |
| e = ldap_next_entry(sp->ldap, e); |
| if (!e) { |
| debug(ap->logopt, MODPREFIX |
| "failed to get next entry for query %s", |
| sp->query); |
| ret = ldap_parse_result(sp->ldap, |
| sp->result, &rv, |
| NULL, NULL, NULL, NULL, 0); |
| if (ret == LDAP_SUCCESS) |
| return rv; |
| else |
| return LDAP_OPERATIONS_ERROR; |
| } |
| continue; |
| } |
| |
| /* By definition keys should be unique within each map entry */ |
| k_val = NULL; |
| k_len = 0; |
| |
| count = ldap_count_values_len(bvKey); |
| if (count > 1) |
| warn(ap->logopt, MODPREFIX |
| "more than one %s, using first", entry); |
| |
| k_val = bvKey[0]->bv_val; |
| k_len = bvKey[0]->bv_len; |
| |
| bvValues = ldap_get_values_len(sp->ldap, e, value); |
| if (!bvValues || !*bvValues) { |
| debug(ap->logopt, |
| MODPREFIX "no %s defined for %s", |
| value, sp->query); |
| goto next; |
| } |
| |
| count = ldap_count_values_len(bvValues); |
| if (count > 1) |
| warn(ap->logopt, MODPREFIX |
| "more than one %s, using first", value); |
| |
| v_val = bvValues[0]->bv_val; |
| |
| /* Don't fail on "/" in key => type == 0 */ |
| s_key = sanitize_path(k_val, k_len, 0, ap->logopt); |
| if (!s_key) |
| goto next; |
| |
| cache_writelock(mc); |
| cache_update(mc, source, s_key, v_val, sp->age); |
| cache_unlock(mc); |
| |
| free(s_key); |
| next: |
| ldap_value_free_len(bvValues); |
| ldap_value_free_len(bvKey); |
| e = ldap_next_entry(sp->ldap, e); |
| if (!e) { |
| debug(ap->logopt, MODPREFIX |
| "failed to get next entry for query %s", |
| sp->query); |
| ret = ldap_parse_result(sp->ldap, |
| sp->result, &rv, |
| NULL, NULL, NULL, NULL, 0); |
| if (ret == LDAP_SUCCESS) |
| return rv; |
| else |
| return LDAP_OPERATIONS_ERROR; |
| } |
| } |
| |
| return LDAP_SUCCESS; |
| } |
| |
| static int read_one_map(struct autofs_point *ap, |
| struct map_source *source, |
| struct lookup_context *ctxt, |
| time_t age, int *result_ldap) |
| { |
| struct ldap_conn conn; |
| struct ldap_search_params sp; |
| char buf[MAX_ERR_BUF]; |
| char *class, *info, *entry; |
| char *attrs[3]; |
| int rv, l; |
| |
| /* |
| * If we don't need to create directories (or don't need |
| * to read an amd cache:=all map) then there's no use |
| * reading the map. We always need to read the whole map |
| * for direct mounts in order to mount the triggers. |
| */ |
| if (ap->type != LKP_DIRECT && |
| !(ap->flags & (MOUNT_FLAG_GHOST|MOUNT_FLAG_AMD_CACHE_ALL))) { |
| debug(ap->logopt, "map read not needed, so not done"); |
| return NSS_STATUS_SUCCESS; |
| } |
| |
| sp.ap = ap; |
| sp.age = age; |
| |
| /* Initialize the LDAP context. */ |
| memset(&conn, 0, sizeof(struct ldap_conn)); |
| rv = do_reconnect(ap->logopt, &conn, ctxt); |
| if (rv) |
| return rv; |
| sp.ldap = conn.ldap; |
| |
| class = ctxt->schema->entry_class; |
| entry = ctxt->schema->entry_attr; |
| info = ctxt->schema->value_attr; |
| |
| attrs[0] = entry; |
| attrs[1] = info; |
| attrs[2] = NULL; |
| sp.attrs = attrs; |
| |
| /* Build a query string. */ |
| l = strlen("(objectclass=)") + strlen(class) + 1; |
| |
| sp.query = malloc(l); |
| if (sp.query == NULL) { |
| char *estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "malloc: %s", estr); |
| return NSS_STATUS_UNAVAIL; |
| } |
| |
| if (sprintf(sp.query, "(objectclass=%s)", class) >= l) { |
| error(ap->logopt, MODPREFIX "error forming query string"); |
| free(sp.query); |
| return NSS_STATUS_UNAVAIL; |
| } |
| |
| if (ctxt->format & MAP_FLAG_FORMAT_AMD) |
| sp.base = ctxt->base; |
| else |
| sp.base = ctxt->qdn; |
| |
| /* Look around. */ |
| debug(ap->logopt, |
| MODPREFIX "searching for \"%s\" under \"%s\"", sp.query, sp.base); |
| |
| sp.cookie = NULL; |
| sp.pageSize = 2000; |
| sp.morePages = FALSE; |
| sp.totalCount = 0; |
| sp.result = NULL; |
| |
| do { |
| rv = do_paged_query(&sp, ctxt); |
| |
| if (rv == LDAP_ADMINLIMIT_EXCEEDED || |
| rv == LDAP_SIZELIMIT_EXCEEDED) { |
| if (sp.result) { |
| ldap_msgfree(sp.result); |
| sp.result = NULL; |
| } |
| if (sp.cookie) { |
| ber_bvfree(sp.cookie); |
| sp.cookie = NULL; |
| } |
| sp.pageSize = sp.pageSize / 2; |
| if (sp.pageSize < 5) { |
| debug(ap->logopt, MODPREFIX |
| "result size too small"); |
| unbind_ldap_connection(ap->logopt, &conn, ctxt); |
| *result_ldap = rv; |
| free(sp.query); |
| return NSS_STATUS_UNAVAIL; |
| } |
| continue; |
| } |
| |
| if (rv != LDAP_SUCCESS || !sp.result) { |
| unbind_ldap_connection(ap->logopt, &conn, ctxt); |
| *result_ldap = rv; |
| if (sp.result) |
| ldap_msgfree(sp.result); |
| if (sp.cookie) |
| ber_bvfree(sp.cookie); |
| free(sp.query); |
| return NSS_STATUS_UNAVAIL; |
| } |
| |
| if (source->flags & MAP_FLAG_FORMAT_AMD) |
| rv = do_get_amd_entries(&sp, source, ctxt); |
| else |
| rv = do_get_entries(&sp, source, ctxt); |
| if (rv != LDAP_SUCCESS) { |
| ldap_msgfree(sp.result); |
| unbind_ldap_connection(ap->logopt, &conn, ctxt); |
| *result_ldap = rv; |
| if (sp.cookie) |
| ber_bvfree(sp.cookie); |
| free(sp.query); |
| return NSS_STATUS_NOTFOUND; |
| } |
| ldap_msgfree(sp.result); |
| sp.result = NULL; |
| } while (sp.morePages == TRUE); |
| |
| debug(ap->logopt, MODPREFIX "done updating map"); |
| |
| unbind_ldap_connection(ap->logopt, &conn, ctxt); |
| |
| source->age = age; |
| if (sp.cookie) |
| ber_bvfree(sp.cookie); |
| free(sp.query); |
| |
| return NSS_STATUS_SUCCESS; |
| } |
| |
| int lookup_read_map(struct autofs_point *ap, time_t age, void *context) |
| { |
| struct lookup_context *ctxt = (struct lookup_context *) context; |
| struct map_source *source; |
| int rv = LDAP_SUCCESS; |
| int ret, cur_state; |
| |
| source = ap->entry->current; |
| ap->entry->current = NULL; |
| master_source_current_signal(ap->entry); |
| |
| pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); |
| ret = read_one_map(ap, source, ctxt, age, &rv); |
| if (ret != NSS_STATUS_SUCCESS) { |
| switch (rv) { |
| case LDAP_SIZELIMIT_EXCEEDED: |
| crit(ap->logopt, MODPREFIX |
| "Unable to download entire LDAP map for: %s", |
| ap->path); |
| case LDAP_UNWILLING_TO_PERFORM: |
| pthread_setcancelstate(cur_state, NULL); |
| return NSS_STATUS_UNAVAIL; |
| } |
| } |
| pthread_setcancelstate(cur_state, NULL); |
| |
| return ret; |
| } |
| |
| static int lookup_one(struct autofs_point *ap, struct map_source *source, |
| char *qKey, int qKey_len, struct lookup_context *ctxt) |
| { |
| struct mapent_cache *mc; |
| struct ldap_conn conn; |
| LDAP *ldap; |
| int rv, i, l, ql, count; |
| char buf[MAX_ERR_BUF]; |
| time_t age = monotonic_time(NULL); |
| char *query; |
| LDAPMessage *result = NULL, *e; |
| char *class, *info, *entry; |
| char *enc_key1, *enc_key2; |
| int enc_len1 = 0, enc_len2 = 0; |
| struct berval **bvKey; |
| struct berval **bvValues; |
| char *attrs[3]; |
| int scope = LDAP_SCOPE_SUBTREE; |
| struct mapent *we; |
| unsigned int wild = 0; |
| int ret = CHE_MISSING; |
| |
| mc = source->mc; |
| |
| if (ctxt == NULL) { |
| crit(ap->logopt, MODPREFIX "context was NULL"); |
| return CHE_FAIL; |
| } |
| |
| /* Initialize the LDAP context. */ |
| memset(&conn, 0, sizeof(struct ldap_conn)); |
| rv = do_reconnect(ap->logopt, &conn, ctxt); |
| if (rv == NSS_STATUS_UNAVAIL) |
| return CHE_UNAVAIL; |
| if (rv == NSS_STATUS_NOTFOUND) |
| return ret; |
| ldap = conn.ldap; |
| |
| class = ctxt->schema->entry_class; |
| entry = ctxt->schema->entry_attr; |
| info = ctxt->schema->value_attr; |
| |
| attrs[0] = entry; |
| attrs[1] = info; |
| attrs[2] = NULL; |
| |
| if (*qKey == '*' && qKey_len == 1) |
| *qKey = '/'; |
| else if (!strcasecmp(class, "nisObject")) { |
| enc_len1 = encode_percent_hack(qKey, &enc_key1, 0); |
| if (enc_len1 < 0) { |
| crit(ap->logopt, |
| "could not use percent hack encode key %s", |
| qKey); |
| return CHE_FAIL; |
| } |
| if (enc_len1 != 0) { |
| enc_len2 = encode_percent_hack(qKey, &enc_key2, 1); |
| if (enc_len2 < 0) { |
| free(enc_key1); |
| crit(ap->logopt, |
| "could not use percent hack encode key %s", |
| qKey); |
| return CHE_FAIL; |
| } |
| } |
| } |
| |
| /* Build a query string. */ |
| l = strlen(class) + 3*strlen(entry) + strlen(qKey) + 35; |
| if (enc_len1) |
| l += 2*strlen(entry) + enc_len1 + enc_len2 + 6; |
| |
| query = malloc(l); |
| if (query == NULL) { |
| char *estr = strerror_r(errno, buf, sizeof(buf)); |
| crit(ap->logopt, MODPREFIX "malloc: %s", estr); |
| if (enc_len1) { |
| free(enc_key1); |
| free(enc_key2); |
| } |
| free(query); |
| return CHE_FAIL; |
| } |
| |
| /* |
| * Look for an entry in class under ctxt-base |
| * whose entry is equal to qKey. |
| */ |
| if (!enc_len1) { |
| ql = sprintf(query, |
| "(&(objectclass=%s)(|(%s=%s)(%s=/)(%s=\\2A)))", |
| class, entry, qKey, entry, entry); |
| } else { |
| if (enc_len2) { |
| ql = sprintf(query, |
| "(&(objectclass=%s)" |
| "(|(%s=%s)(%s=%s)(%s=%s)(%s=/)(%s=\\2A)))", |
| class, entry, qKey, |
| entry, enc_key1, entry, enc_key2, entry, entry); |
| free(enc_key1); |
| free(enc_key2); |
| } else { |
| ql = sprintf(query, |
| "(&(objectclass=%s)" |
| "(|(%s=%s)(%s=%s)(%s=/)(%s=\\2A)))", |
| class, entry, qKey, entry, enc_key1, entry, entry); |
| free(enc_key1); |
| } |
| } |
| if (ql >= l) { |
| error(ap->logopt, |
| MODPREFIX "error forming query string"); |
| free(query); |
| return CHE_FAIL; |
| } |
| |
| debug(ap->logopt, |
| MODPREFIX "searching for \"%s\" under \"%s\"", query, ctxt->qdn); |
| |
| rv = ldap_search_s(ldap, ctxt->qdn, scope, query, attrs, 0, &result); |
| |
| if ((rv != LDAP_SUCCESS) || !result) { |
| crit(ap->logopt, MODPREFIX "query failed for %s", query); |
| unbind_ldap_connection(ap->logopt, &conn, ctxt); |
| if (result) |
| ldap_msgfree(result); |
| free(query); |
| return CHE_FAIL; |
| } |
| |
| debug(ap->logopt, |
| MODPREFIX "getting first entry for %s=\"%s\"", entry, qKey); |
| |
| e = ldap_first_entry(ldap, result); |
| if (!e) { |
| debug(ap->logopt, |
| MODPREFIX "got answer, but no entry for %s", query); |
| ldap_msgfree(result); |
| unbind_ldap_connection(ap->logopt, &conn, ctxt); |
| free(query); |
| return CHE_MISSING; |
| } |
| |
| while (e) { |
| char *mapent = NULL; |
| size_t mapent_len = 0; |
| char *k_val; |
| ber_len_t k_len; |
| char *s_key; |
| |
| bvKey = ldap_get_values_len(ldap, e, entry); |
| if (!bvKey || !*bvKey) { |
| e = ldap_next_entry(ldap, e); |
| continue; |
| } |
| |
| /* |
| * By definition keys should be unique within each map entry, |
| * but as always there are exceptions. |
| */ |
| k_val = NULL; |
| k_len = 0; |
| |
| /* |
| * Keys must be unique so, in general, there shouldn't be |
| * more than one attribute value. We make an exception for |
| * wildcard entries as people may have values for '*' or |
| * '/' for compaibility reasons. We use the '/' as the |
| * wildcard in LDAP but allow '*' as well to allow for |
| * people using older schemas that allow '*' as a key |
| * value. Another case where there can be multiple key |
| * values is when people have used the "%" hack to specify |
| * case matching ctriteria in a caase insensitive attribute. |
| */ |
| count = ldap_count_values_len(bvKey); |
| if (count > 1) { |
| unsigned int i; |
| |
| /* Check for the "/" and "*" and use as "/" if found */ |
| for (i = 0; i < count; i++) { |
| bvKey[i]->bv_val[bvKey[i]->bv_len] = '\0'; |
| |
| /* |
| * If multiple entries are present they could |
| * be the result of people using the "%" hack so |
| * ignore them. |
| */ |
| if (strchr(bvKey[i]->bv_val, '%')) |
| continue; |
| |
| /* check for wildcard */ |
| if (bvKey[i]->bv_len == 1 && |
| (*bvKey[i]->bv_val == '/' || |
| *bvKey[i]->bv_val == '*')) { |
| /* always use '/' internally */ |
| *bvKey[i]->bv_val = '/'; |
| k_val = bvKey[i]->bv_val; |
| k_len = 1; |
| break; |
| } |
| |
| /* |
| * The key was matched by LDAP so this is a |
| * valid entry. Set the result key to the |
| * lookup key to provide the mixed case |
| * matching provided by the "%" hack. |
| */ |
| k_val = qKey; |
| k_len = strlen(qKey); |
| |
| break; |
| } |
| |
| if (!k_val) { |
| error(ap->logopt, |
| MODPREFIX "no valid key found for %.*s", |
| qKey_len, qKey); |
| ret = CHE_FAIL; |
| goto next; |
| } |
| } else { |
| /* Check for the "*" and use as "/" if found */ |
| if (bvKey[0]->bv_len == 1 && *bvKey[0]->bv_val == '*') |
| *bvKey[0]->bv_val = '/'; |
| k_val = bvKey[0]->bv_val; |
| k_len = bvKey[0]->bv_len; |
| } |
| |
| debug(ap->logopt, MODPREFIX "examining first entry"); |
| |
| bvValues = ldap_get_values_len(ldap, e, info); |
| if (!bvValues || !*bvValues) { |
| debug(ap->logopt, |
| MODPREFIX "no %s defined for %s", info, query); |
| goto next; |
| } |
| |
| count = ldap_count_values_len(bvValues); |
| for (i = 0; i < count; i++) { |
| char *v_val = bvValues[i]->bv_val; |
| ber_len_t v_len = bvValues[i]->bv_len; |
| |
| if (!mapent) { |
| mapent = malloc(v_len + 1); |
| if (!mapent) { |
| char *estr; |
| estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "malloc: %s", estr); |
| ldap_value_free_len(bvValues); |
| goto next; |
| } |
| strncpy(mapent, v_val, v_len); |
| mapent[v_len] = '\0'; |
| mapent_len = v_len; |
| } else { |
| int new_size = mapent_len + v_len + 2; |
| char *new_me; |
| new_me = realloc(mapent, new_size); |
| if (new_me) { |
| mapent = new_me; |
| strcat(mapent, " "); |
| strncat(mapent, v_val, v_len); |
| mapent[new_size - 1] = '\0'; |
| mapent_len = new_size - 1; |
| } else { |
| char *estr; |
| estr = strerror_r(errno, buf, sizeof(buf)); |
| logerr(MODPREFIX "realloc: %s", estr); |
| } |
| } |
| } |
| ldap_value_free_len(bvValues); |
| |
| if (*k_val == '/' && k_len == 1) { |
| if (ap->type == LKP_DIRECT) |
| goto next; |
| wild = 1; |
| cache_writelock(mc); |
| cache_update(mc, source, "*", mapent, age); |
| cache_unlock(mc); |
| goto next; |
| } |
| |
| if (strcasecmp(class, "nisObject")) { |
| s_key = sanitize_path(k_val, k_len, ap->type, ap->logopt); |
| if (!s_key) |
| goto next; |
| } else { |
| char *dec_key; |
| int dec_len = decode_percent_hack(k_val, &dec_key); |
| |
| if (dec_len < 0) { |
| crit(ap->logopt, |
| "could not use percent hack to decode key %s", |
| k_val); |
| goto next; |
| } |
| |
| if (dec_len == 0) |
| s_key = sanitize_path(k_val, k_len, ap->type, ap->logopt); |
| else { |
| s_key = sanitize_path(dec_key, dec_len, ap->type, ap->logopt); |
| free(dec_key); |
| } |
| if (!s_key) |
| goto next; |
| } |
| |
| cache_writelock(mc); |
| ret = cache_update(mc, source, s_key, mapent, age); |
| cache_unlock(mc); |
| |
| free(s_key); |
| next: |
| if (mapent) { |
| free(mapent); |
| mapent = NULL; |
| } |
| |
| ldap_value_free_len(bvKey); |
| e = ldap_next_entry(ldap, e); |
| } |
| |
| ldap_msgfree(result); |
| unbind_ldap_connection(ap->logopt, &conn, ctxt); |
| |
| /* Failed to find wild entry, update cache if needed */ |
| cache_writelock(mc); |
| we = cache_lookup_distinct(mc, "*"); |
| if (we) { |
| /* Wildcard entry existed and is now gone */ |
| if (we->source == source && !wild) { |
| cache_delete(mc, "*"); |
| source->stale = 1; |
| } |
| } else { |
| /* Wildcard not in map but now is */ |
| if (wild) |
| source->stale = 1; |
| } |
| /* Not found in the map but found in the cache */ |
| if (ret == CHE_MISSING) { |
| struct mapent *exists = cache_lookup_distinct(mc, qKey); |
| if (exists && exists->source == source) { |
| if (exists->mapent) { |
| free(exists->mapent); |
| exists->mapent = NULL; |
| source->stale = 1; |
| exists->status = 0; |
| } |
| } |
| } |
| cache_unlock(mc); |
| free(query); |
| |
| return ret; |
| } |
| |
| static int lookup_one_amd(struct autofs_point *ap, |
| struct map_source *source, |
| char *qKey, int qKey_len, |
| struct lookup_context *ctxt) |
| { |
| struct mapent_cache *mc = source->mc; |
| struct ldap_conn conn; |
| LDAP *ldap; |
| LDAPMessage *result = NULL, *e; |
| char *query; |
| int scope = LDAP_SCOPE_SUBTREE; |
| char *map, *class, *entry, *value; |
| char *attrs[3]; |
| struct berval **bvKey; |
| struct berval **bvValues; |
| char buf[MAX_ERR_BUF]; |
| time_t age = monotonic_time(NULL); |
| int rv, l, ql, count; |
| int ret = CHE_MISSING; |
| |
| if (ctxt == NULL) { |
| crit(ap->logopt, MODPREFIX "context was NULL"); |
| return CHE_FAIL; |
| } |
| |
| /* Initialize the LDAP context. */ |
| memset(&conn, 0, sizeof(struct ldap_conn)); |
| rv = do_reconnect(ap->logopt, &conn, ctxt); |
| if (rv == NSS_STATUS_UNAVAIL) |
| return CHE_UNAVAIL; |
| if (rv == NSS_STATUS_NOTFOUND) |
| return ret; |
| ldap = conn.ldap; |
| |
| map = ctxt->schema->map_attr; |
| class = ctxt->schema->entry_class; |
| entry = ctxt->schema->entry_attr; |
| value = ctxt->schema->value_attr; |
| |
| attrs[0] = entry; |
| attrs[1] = value; |
| attrs[2] = NULL; |
| |
| /* Build a query string. */ |
| l = strlen(class) + |
| strlen(map) + strlen(ctxt->mapname) + |
| strlen(entry) + strlen(qKey) + 24; |
| |
| query = malloc(l); |
| if (query == NULL) { |
| char *estr = strerror_r(errno, buf, sizeof(buf)); |
| crit(ap->logopt, MODPREFIX "malloc: %s", estr); |
| return CHE_FAIL; |
| } |
| |
| /* |
| * Look for an entry in class under ctxt-base |
| * whose entry is equal to qKey. |
| */ |
| ql = sprintf(query, "(&(objectclass=%s)(%s=%s)(%s=%s))", |
| class, map, ctxt->mapname, entry, qKey); |
| if (ql >= l) { |
| error(ap->logopt, |
| MODPREFIX "error forming query string"); |
| free(query); |
| return CHE_FAIL; |
| } |
| |
| debug(ap->logopt, |
| MODPREFIX "searching for \"%s\" under \"%s\"", query, ctxt->base); |
| |
| rv = ldap_search_s(ldap, ctxt->base, scope, query, attrs, 0, &result); |
| if ((rv != LDAP_SUCCESS) || !result) { |
| crit(ap->logopt, MODPREFIX "query failed for %s", query); |
| unbind_ldap_connection(ap->logopt, &conn, ctxt); |
| if (result) |
| ldap_msgfree(result); |
| free(query); |
| return CHE_FAIL; |
| } |
| |
| debug(ap->logopt, |
| MODPREFIX "getting first entry for %s=\"%s\"", entry, qKey); |
| |
| e = ldap_first_entry(ldap, result); |
| if (!e) { |
| debug(ap->logopt, |
| MODPREFIX "got answer, but no entry for %s", query); |
| ldap_msgfree(result); |
| unbind_ldap_connection(ap->logopt, &conn, ctxt); |
| free(query); |
| return CHE_MISSING; |
| } |
| |
| while (e) { |
| char *k_val, *v_val; |
| ber_len_t k_len; |
| char *s_key; |
| |
| bvKey = ldap_get_values_len(ldap, e, entry); |
| if (!bvKey || !*bvKey) { |
| e = ldap_next_entry(ldap, e); |
| continue; |
| } |
| |
| /* By definition keys should be unique within each map entry */ |
| k_val = NULL; |
| k_len = 0; |
| |
| count = ldap_count_values_len(bvKey); |
| if (count > 1) |
| warn(ap->logopt, MODPREFIX |
| "more than one %s, using first", entry); |
| |
| k_val = bvKey[0]->bv_val; |
| k_len = bvKey[0]->bv_len; |
| |
| debug(ap->logopt, MODPREFIX "examining first entry"); |
| |
| bvValues = ldap_get_values_len(ldap, e, value); |
| if (!bvValues || !*bvValues) { |
| debug(ap->logopt, |
| MODPREFIX "no %s defined for %s", value, query); |
| goto next; |
| } |
| |
| count = ldap_count_values_len(bvValues); |
| if (count > 1) |
| warn(ap->logopt, MODPREFIX |
| "more than one %s, using first", value); |
| |
| /* There should be one value for a key, use first value */ |
| v_val = bvValues[0]->bv_val; |
| |
| /* Don't fail on "/" in key => type == 0 */ |
| s_key = sanitize_path(k_val, k_len, 0, ap->logopt); |
| if (!s_key) |
| goto next; |
| |
| cache_writelock(mc); |
| ret = cache_update(mc, source, s_key, v_val, age); |
| cache_unlock(mc); |
| |
| free(s_key); |
| next: |
| ldap_value_free_len(bvValues); |
| ldap_value_free_len(bvKey); |
| e = ldap_next_entry(ldap, e); |
| } |
| |
| ldap_msgfree(result); |
| unbind_ldap_connection(ap->logopt, &conn, ctxt); |
| free(query); |
| |
| return ret; |
| } |
| |
| static int match_key(struct autofs_point *ap, |
| struct map_source *source, |
| char *key, int key_len, |
| struct lookup_context *ctxt) |
| { |
| unsigned int is_amd_format = source->flags & MAP_FLAG_FORMAT_AMD; |
| char buf[MAX_ERR_BUF]; |
| char *lkp_key; |
| char *prefix; |
| int ret; |
| |
| if (is_amd_format) |
| ret = lookup_one_amd(ap, source, key, key_len, ctxt); |
| else |
| ret = lookup_one(ap, source, key, key_len, ctxt); |
| |
| if (ret == CHE_OK || ret == CHE_UPDATED || !is_amd_format) |
| return ret; |
| |
| lkp_key = strdup(key); |
| if (!lkp_key) { |
| char *estr = strerror_r(errno, buf, MAX_ERR_BUF); |
| error(ap->logopt, MODPREFIX "strdup: %s", estr); |
| return CHE_FAIL; |
| } |
| |
| ret = CHE_MISSING; |
| |
| /* |
| * Now strip successive directory components and try a |
| * match against map entries ending with a wildcard and |
| * finally try the wilcard entry itself. |
| */ |
| while ((prefix = strrchr(lkp_key, '/'))) { |
| char *match; |
| size_t len; |
| *prefix = '\0'; |
| len = strlen(lkp_key + 3); |
| match = malloc(len); |
| if (!match) { |
| char *estr = strerror_r(errno, buf, MAX_ERR_BUF); |
| error(ap->logopt, MODPREFIX "malloc: %s", estr); |
| ret = CHE_FAIL; |
| goto done; |
| } |
| len--; |
| strcpy(match, lkp_key); |
| strcat(match, "/*"); |
| ret = lookup_one_amd(ap, source, match, len, ctxt); |
| free(match); |
| if (ret == CHE_OK || ret == CHE_UPDATED) |
| goto done; |
| } |
| done: |
| free(lkp_key); |
| return ret; |
| } |
| |
| static int check_map_indirect(struct autofs_point *ap, |
| struct map_source *source, |
| char *key, int key_len, |
| struct lookup_context *ctxt) |
| { |
| unsigned int is_amd_format = source->flags & MAP_FLAG_FORMAT_AMD; |
| struct mapent_cache *mc; |
| struct mapent *me; |
| time_t now = monotonic_time(NULL); |
| time_t t_last_read; |
| int ret, cur_state; |
| int status; |
| |
| mc = source->mc; |
| |
| pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); |
| |
| status = pthread_mutex_lock(&ap->entry->current_mutex); |
| if (status) |
| fatal(status); |
| if (is_amd_format) { |
| unsigned long timestamp = get_amd_timestamp(ctxt); |
| if (timestamp > ctxt->timestamp) { |
| ctxt->timestamp = timestamp; |
| source->stale = 1; |
| ctxt->check_defaults = 1; |
| } |
| |
| if (ctxt->check_defaults) { |
| /* Check for a /defaults entry */ |
| ret = lookup_one_amd(ap, source, "/defaults", 9, ctxt); |
| if (ret == CHE_FAIL) { |
| warn(ap->logopt, MODPREFIX |
| "error getting /defaults from map %s", |
| ctxt->mapname); |
| } else |
| ctxt->check_defaults = 0; |
| } |
| } |
| status = pthread_mutex_unlock(&ap->entry->current_mutex); |
| if (status) |
| fatal(status); |
| |
| ret = match_key(ap, source, key, key_len, ctxt); |
| if (ret == CHE_FAIL) { |
| pthread_setcancelstate(cur_state, NULL); |
| return NSS_STATUS_NOTFOUND; |
| } else if (ret == CHE_UNAVAIL) { |
| struct mapent *exists; |
| /* |
| * If the server is down and the entry exists in the cache |
| * and belongs to this map return success and use the entry. |
| */ |
| if (source->flags & MAP_FLAG_FORMAT_AMD) |
| exists = match_cached_key(ap, MODPREFIX, source, key); |
| else |
| exists = cache_lookup(mc, key); |
| if (exists && exists->source == source) { |
| pthread_setcancelstate(cur_state, NULL); |
| return NSS_STATUS_SUCCESS; |
| } |
| |
| warn(ap->logopt, |
| MODPREFIX "lookup for %s failed: connection failed", key); |
| |
| return NSS_STATUS_UNAVAIL; |
| } |
| pthread_setcancelstate(cur_state, NULL); |
| |
| if (!is_amd_format) { |
| /* |
| * Check for map change and update as needed for |
| * following cache lookup. |
| */ |
| cache_readlock(mc); |
| t_last_read = ap->exp_runfreq + 1; |
| me = cache_lookup_first(mc); |
| while (me) { |
| if (me->source == source) { |
| t_last_read = now - me->age; |
| break; |
| } |
| me = cache_lookup_next(mc, me); |
| } |
| cache_unlock(mc); |
| |
| status = pthread_mutex_lock(&ap->entry->current_mutex); |
| if (status) |
| fatal(status); |
| if (t_last_read > ap->exp_runfreq && ret & CHE_UPDATED) |
| source->stale = 1; |
| status = pthread_mutex_unlock(&ap->entry->current_mutex); |
| if (status) |
| fatal(status); |
| } |
| |
| cache_readlock(mc); |
| me = cache_lookup_distinct(mc, "*"); |
| if (ret == CHE_MISSING && (!me || me->source != source)) { |
| cache_unlock(mc); |
| return NSS_STATUS_NOTFOUND; |
| } |
| cache_unlock(mc); |
| |
| return NSS_STATUS_SUCCESS; |
| } |
| |
| int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) |
| { |
| struct lookup_context *ctxt = (struct lookup_context *) context; |
| struct map_source *source; |
| struct mapent_cache *mc; |
| struct mapent *me; |
| char key[KEY_MAX_LEN + 1]; |
| int key_len; |
| char *lkp_key; |
| char *mapent = NULL; |
| char mapent_buf[MAPENT_MAX_LEN + 1]; |
| char buf[MAX_ERR_BUF]; |
| int status = 0; |
| int ret = 1; |
| |
| source = ap->entry->current; |
| ap->entry->current = NULL; |
| master_source_current_signal(ap->entry); |
| |
| mc = source->mc; |
| |
| debug(ap->logopt, MODPREFIX "looking up %s", name); |
| |
| if (!(source->flags & MAP_FLAG_FORMAT_AMD)) { |
| key_len = snprintf(key, KEY_MAX_LEN + 1, "%s", name); |
| if (key_len > KEY_MAX_LEN) |
| return NSS_STATUS_NOTFOUND; |
| } else { |
| key_len = expandamdent(name, NULL, NULL); |
| if (key_len > KEY_MAX_LEN) |
| return NSS_STATUS_NOTFOUND; |
| expandamdent(name, key, NULL); |
| key[key_len] = '\0'; |
| debug(ap->logopt, MODPREFIX "expanded key: \"%s\"", key); |
| } |
| |
| /* Check if we recorded a mount fail for this key anywhere */ |
| me = lookup_source_mapent(ap, key, LKP_DISTINCT); |
| if (me) { |
| if (me->status >= monotonic_time(NULL)) { |
| cache_unlock(me->mc); |
| return NSS_STATUS_NOTFOUND; |
| } else { |
| struct mapent_cache *smc = me->mc; |
| struct mapent *sme; |
| |
| if (me->mapent) |
| cache_unlock(smc); |
| else { |
| cache_unlock(smc); |
| cache_writelock(smc); |
| sme = cache_lookup_distinct(smc, key); |
| /* Negative timeout expired for non-existent entry. */ |
| if (sme && !sme->mapent) { |
| if (cache_pop_mapent(sme) == CHE_FAIL) |
| cache_delete(smc, key); |
| } |
| cache_unlock(smc); |
| } |
| } |
| } |
| |
| /* |
| * We can't check the direct mount map as if it's not in |
| * the map cache already we never get a mount lookup, so |
| * we never know about it. |
| */ |
| if (ap->type == LKP_INDIRECT && *key != '/') { |
| cache_readlock(mc); |
| me = cache_lookup_distinct(mc, key); |
| if (me && me->multi) |
| lkp_key = strdup(me->multi->key); |
| else if (!ap->pref) |
| lkp_key = strdup(key); |
| else { |
| lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1); |
| if (lkp_key) { |
| strcpy(lkp_key, ap->pref); |
| strcat(lkp_key, key); |
| } |
| } |
| cache_unlock(mc); |
| |
| if (!lkp_key) { |
| char *estr = strerror_r(errno, buf, MAX_ERR_BUF); |
| error(ap->logopt, MODPREFIX "malloc: %s", estr); |
| return NSS_STATUS_UNKNOWN; |
| } |
| |
| status = check_map_indirect(ap, source, |
| lkp_key, strlen(lkp_key), ctxt); |
| free(lkp_key); |
| if (status) |
| return status; |
| } |
| |
| /* |
| * We can't take the writelock for direct mounts. If we're |
| * starting up or trying to re-connect to an existing direct |
| * mount we could be iterating through the map entries with |
| * the readlock held. But we don't need to update the cache |
| * when we're starting up so just take the readlock in that |
| * case. |
| */ |
| if (ap->flags & MOUNT_FLAG_REMOUNT) |
| cache_readlock(mc); |
| else |
| cache_writelock(mc); |
| |
| if (!ap->pref) |
| lkp_key = strdup(key); |
| else { |
| lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1); |
| if (lkp_key) { |
| strcpy(lkp_key, ap->pref); |
| strcat(lkp_key, key); |
| } |
| } |
| |
| if (!lkp_key) { |
| char *estr = strerror_r(errno, buf, MAX_ERR_BUF); |
| error(ap->logopt, MODPREFIX "malloc: %s", estr); |
| cache_unlock(mc); |
| return NSS_STATUS_UNKNOWN; |
| } |
| |
| me = match_cached_key(ap, MODPREFIX, source, lkp_key); |
| /* Stale mapent => check for entry in alternate source or wildcard */ |
| if (me && !me->mapent) { |
| while ((me = cache_lookup_key_next(me))) |
| if (me->source == source) |
| break; |
| if (!me) |
| me = cache_lookup_distinct(mc, "*"); |
| } |
| if (me && me->mapent) { |
| /* |
| * If this is a lookup add wildcard match for later validation |
| * checks and negative cache lookups. |
| */ |
| if (!(ap->flags & MOUNT_FLAG_REMOUNT) && |
| ap->type == LKP_INDIRECT && *me->key == '*') { |
| ret = cache_update(mc, source, key, me->mapent, me->age); |
| if (!(ret & (CHE_OK | CHE_UPDATED))) |
| me = NULL; |
| } |
| if (me && (me->source == source || *me->key == '/')) { |
| strcpy(mapent_buf, me->mapent); |
| mapent = mapent_buf; |
| } |
| } |
| cache_unlock(mc); |
| free(lkp_key); |
| |
| if (!mapent) |
| return NSS_STATUS_TRYAGAIN; |
| |
| master_source_current_wait(ap->entry); |
| ap->entry->current = source; |
| |
| debug(ap->logopt, MODPREFIX "%s -> %s", key, mapent); |
| ret = ctxt->parse->parse_mount(ap, key, key_len, |
| mapent, ctxt->parse->context); |
| if (ret) { |
| /* Don't update negative cache when re-connecting */ |
| if (ap->flags & MOUNT_FLAG_REMOUNT) |
| return NSS_STATUS_TRYAGAIN; |
| cache_writelock(mc); |
| cache_update_negative(mc, source, key, ap->negative_timeout); |
| cache_unlock(mc); |
| return NSS_STATUS_TRYAGAIN; |
| } |
| |
| return NSS_STATUS_SUCCESS; |
| } |
| |
| /* |
| * This destroys a context for queries to this module. It releases the parser |
| * structure (unloading the module) and frees the memory used by the context. |
| */ |
| int lookup_done(void *context) |
| { |
| struct lookup_context *ctxt = (struct lookup_context *) context; |
| int rv = close_parse(ctxt->parse); |
| #ifdef WITH_SASL |
| ldapinit_mutex_lock(); |
| autofs_sasl_dispose(NULL, ctxt); |
| autofs_sasl_done(); |
| ldapinit_mutex_unlock(); |
| #endif |
| free_context(ctxt); |
| return rv; |
| } |