blob: 98701e5b5b6852e44566d61ffaac96a14f86a0ac [file] [log] [blame]
/*
* 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;
}