/* $NetBSD: info_ldap.c,v 1.2 2015/08/28 11:38:57 joerg Exp $ */
/*
* Copyright (c) 1997-2014 Erez Zadok
* Copyright (c) 1989 Jan-Simon Pendry
* Copyright (c) 1989 Imperial College of Science, Technology & Medicine
* Copyright (c) 1989 The Regents of the University of California.
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Jan-Simon Pendry at Imperial College, London.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*
* File: am-utils/amd/info_ldap.c
*
*/
/*
* Get info from LDAP (Lightweight Directory Access Protocol)
* LDAP Home Page: http://www.umich.edu/~rsug/ldap/
*/
/*
* WARNING: as of Linux Fedora Core 5 (which comes with openldap-2.3.9), the
* ldap.h headers deprecate several functions used in this file, such as
* ldap_unbind. You get compile errors about missing extern definitions.
* Those externs are still in <ldap.h>, but surrounded by an ifdef
* LDAP_DEPRECATED. I am turning on that ifdef here, under the assumption
* that the functions may be deprecated, but they still work for this
* (older?) version of the LDAP API. It gets am-utils to compile, but it is
* not clear if it will work perfectly.
*/
#ifndef LDAP_DEPRECATED
# define LDAP_DEPRECATED 1
#endif /* not LDAP_DEPRECATED */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <am_defs.h>
#include <amd.h>
#include <sun_map.h>
/*
* MACROS:
*/
#define AMD_LDAP_TYPE "ldap"
/* Time to live for an LDAP cached in an mnt_map */
#define AMD_LDAP_TTL 3600
#define AMD_LDAP_RETRIES 5
#define AMD_LDAP_HOST "ldap"
#ifndef LDAP_PORT
# define LDAP_PORT 389
#endif /* LDAP_PORT */
/* How timestamps are searched */
#define AMD_LDAP_TSFILTER "(&(objectClass=amdmapTimestamp)(amdmapName=%s))"
/* How maps are searched */
#define AMD_LDAP_FILTER "(&(objectClass=amdmap)(amdmapName=%s)(amdmapKey=%s))"
/* How timestamps are stored */
#define AMD_LDAP_TSATTR "amdmaptimestamp"
/* How maps are stored */
#define AMD_LDAP_ATTR "amdmapvalue"
/*
* TYPEDEFS:
*/
typedef struct ald_ent ALD;
typedef struct cr_ent CR;
typedef struct he_ent HE_ENT;
/*
* STRUCTURES:
*/
struct ald_ent {
LDAP *ldap;
HE_ENT *hostent;
CR *credentials;
time_t timestamp;
};
struct cr_ent {
char *who;
char *pw;
int method;
};
struct he_ent {
char *host;
int port;
struct he_ent *next;
};
static ALD *ldap_connection;
/*
* FORWARD DECLARATIONS:
*/
static int amu_ldap_rebind(ALD *a);
static int get_ldap_timestamp(ALD *a, char *map, time_t *ts);
int amu_ldap_init(mnt_map *m, char *map, time_t *tsu);
int amu_ldap_search(mnt_map *m, char *map, char *key, char **pval, time_t *ts);
int amu_ldap_mtime(mnt_map *m, char *map, time_t *ts);
/*
* FUNCTIONS:
*/
static void
he_free(HE_ENT *h)
{
XFREE(h->host);
if (h->next != NULL)
he_free(h->next);
XFREE(h);
}
static HE_ENT *
string2he(char *s_orig)
{
char *c, *p;
char *s;
HE_ENT *first = NULL, *cur = NULL;
if (NULL == s_orig)
return NULL;
s = xstrdup(s_orig);
for (p = strtok(s, ","); p; p = strtok(NULL, ",")) {
if (cur != NULL) {
cur->next = ALLOC(HE_ENT);
cur = cur->next;
} else
first = cur = ALLOC(HE_ENT);
cur->next = NULL;
c = strchr(p, ':');
if (c) { /* Host and port */
*c++ = '\0';
cur->host = xstrdup(p);
cur->port = atoi(c);
} else {
cur->host = xstrdup(p);
cur->port = LDAP_PORT;
}
plog(XLOG_USER, "Adding ldap server %s:%d",
cur->host, cur->port);
}
XFREE(s);
return first;
}
static void
cr_free(CR *c)
{
XFREE(c->who);
XFREE(c->pw);
XFREE(c);
}
/*
* Special ldap_unbind function to handle SIGPIPE.
* We first ignore SIGPIPE, in case a remote LDAP server was
* restarted, then we reinstall the handler.
*/
static int
amu_ldap_unbind(LDAP *ld)
{
int e;
#ifdef HAVE_SIGACTION
struct sigaction sa;
#else /* not HAVE_SIGACTION */
void (*handler)(int);
#endif /* not HAVE_SIGACTION */
dlog("amu_ldap_unbind()\n");
#ifdef HAVE_SIGACTION
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
sigemptyset(&(sa.sa_mask));
sigaddset(&(sa.sa_mask), SIGPIPE);
sigaction(SIGPIPE, &sa, &sa); /* set IGNORE, and get old action */
#else /* not HAVE_SIGACTION */
handler = signal(SIGPIPE, SIG_IGN);
#endif /* not HAVE_SIGACTION */
e = ldap_unbind(ld);
#ifdef HAVE_SIGACTION
sigemptyset(&(sa.sa_mask));
sigaddset(&(sa.sa_mask), SIGPIPE);
sigaction(SIGPIPE, &sa, NULL);
#else /* not HAVE_SIGACTION */
(void) signal(SIGPIPE, handler);
#endif /* not HAVE_SIGACTION */
return e;
}
static void
ald_free(ALD *a)
{
he_free(a->hostent);
cr_free(a->credentials);
if (a->ldap != NULL)
amu_ldap_unbind(a->ldap);
XFREE(a);
}
int
amu_ldap_init(mnt_map *m, char *map, time_t *ts)
{
ALD *aldh;
CR *creds;
dlog("-> amu_ldap_init: map <%s>\n", map);
/*
* XXX: by checking that map_type must be defined, aren't we
* excluding the possibility of automatic searches through all
* map types?
*/
if (!gopt.map_type || !STREQ(gopt.map_type, AMD_LDAP_TYPE)) {
dlog("amu_ldap_init called with map_type <%s>\n",
(gopt.map_type ? gopt.map_type : "null"));
return ENOENT;
} else {
dlog("Map %s is ldap\n", map);
}
#ifndef LDAP_CONNECTION_PER_MAP
if (ldap_connection != NULL) {
m->map_data = (void *) ldap_connection;
return 0;
}
#endif
aldh = ALLOC(ALD);
creds = ALLOC(CR);
aldh->ldap = NULL;
aldh->hostent = string2he(gopt.ldap_hostports);
if (aldh->hostent == NULL) {
plog(XLOG_USER, "Unable to parse hostport %s for ldap map %s",
gopt.ldap_hostports ? gopt.ldap_hostports : "(null)", map);
XFREE(creds);
XFREE(aldh);
return (ENOENT);
}
creds->who = "";
creds->pw = "";
creds->method = LDAP_AUTH_SIMPLE;
aldh->credentials = creds;
aldh->timestamp = 0;
aldh->ldap = NULL;
dlog("Trying for %s:%d\n", aldh->hostent->host, aldh->hostent->port);
if (amu_ldap_rebind(aldh)) {
ald_free(aldh);
return (ENOENT);
}
dlog("Bound to %s:%d\n", aldh->hostent->host, aldh->hostent->port);
if (get_ldap_timestamp(aldh, map, ts)) {
ald_free(aldh);
return (ENOENT);
}
dlog("Got timestamp for map %s: %ld\n", map, (u_long) *ts);
ldap_connection = aldh;
m->map_data = (void *) ldap_connection;
return (0);
}
static int
amu_ldap_rebind(ALD *a)
{
LDAP *ld;
HE_ENT *h;
CR *c = a->credentials;
time_t now = clocktime(NULL);
int try;
dlog("-> amu_ldap_rebind\n");
if (a->ldap != NULL) {
if ((a->timestamp - now) > AMD_LDAP_TTL) {
dlog("Re-establishing ldap connection\n");
amu_ldap_unbind(a->ldap);
a->timestamp = now;
a->ldap = NULL;
} else {
/* Assume all is OK. If it wasn't we'll be back! */
dlog("amu_ldap_rebind: timestamp OK\n");
return (0);
}
}
for (try=0; try<10; try++) { /* XXX: try up to 10 times (makes sense?) */
for (h = a->hostent; h != NULL; h = h->next) {
if ((ld = ldap_open(h->host, h->port)) == NULL) {
plog(XLOG_WARNING, "Unable to ldap_open to %s:%d\n", h->host, h->port);
continue;
}
#if LDAP_VERSION_MAX > LDAP_VERSION2
/* handle LDAPv3 and heigher, if available and amd.conf-igured */
if (gopt.ldap_proto_version > LDAP_VERSION2) {
if (!ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &gopt.ldap_proto_version)) {
dlog("amu_ldap_rebind: LDAP protocol version set to %ld\n",
gopt.ldap_proto_version);
} else {
plog(XLOG_WARNING, "Unable to set ldap protocol version to %ld for "
"%s:%d\n", gopt.ldap_proto_version, h->host, h->port);
continue;
}
}
#endif /* LDAP_VERSION_MAX > LDAP_VERSION2 */
if (ldap_bind_s(ld, c->who, c->pw, c->method) != LDAP_SUCCESS) {
plog(XLOG_WARNING, "Unable to ldap_bind to %s:%d as %s\n",
h->host, h->port, c->who);
continue;
}
if (gopt.ldap_cache_seconds > 0) {
#if defined(HAVE_LDAP_ENABLE_CACHE) && defined(HAVE_EXTERN_LDAP_ENABLE_CACHE)
ldap_enable_cache(ld, gopt.ldap_cache_seconds, gopt.ldap_cache_maxmem);
#else /* not defined(HAVE_LDAP_ENABLE_CACHE) && defined(HAVE_EXTERN_LDAP_ENABLE_CACHE) */
plog(XLOG_WARNING, "ldap_enable_cache(%ld) is not available on this system!\n", gopt.ldap_cache_seconds);
#endif /* not defined(HAVE_LDAP_ENABLE_CACHE) && defined(HAVE_EXTERN_LDAP_ENABLE_CACHE) */
}
a->ldap = ld;
a->timestamp = now;
return (0);
}
plog(XLOG_WARNING, "Exhausted list of ldap servers, looping.\n");
}
plog(XLOG_USER, "Unable to (re)bind to any ldap hosts\n");
return (ENOENT);
}
static int
get_ldap_timestamp(ALD *a, char *map, time_t *ts)
{
struct timeval tv;
char **vals, *end;
char filter[MAXPATHLEN];
int i, err = 0, nentries = 0;
LDAPMessage *res = NULL, *entry;
dlog("-> get_ldap_timestamp: map <%s>\n", map);
tv.tv_sec = 3;
tv.tv_usec = 0;
xsnprintf(filter, sizeof(filter), AMD_LDAP_TSFILTER, map);
dlog("Getting timestamp for map %s\n", map);
dlog("Filter is: %s\n", filter);
dlog("Base is: %s\n", gopt.ldap_base);
for (i = 0; i < AMD_LDAP_RETRIES; i++) {
err = ldap_search_st(a->ldap,
gopt.ldap_base,
LDAP_SCOPE_SUBTREE,
filter,
0,
0,
&tv,
&res);
if (err == LDAP_SUCCESS)
break;
if (res) {
ldap_msgfree(res);
res = NULL;
}
plog(XLOG_USER, "Timestamp LDAP search attempt %d failed: %s\n",
i + 1, ldap_err2string(err));
if (err != LDAP_TIMEOUT) {
dlog("get_ldap_timestamp: unbinding...\n");
amu_ldap_unbind(a->ldap);
a->ldap = NULL;
if (amu_ldap_rebind(a))
return (ENOENT);
}
dlog("Timestamp search failed, trying again...\n");
}
if (err != LDAP_SUCCESS) {
*ts = 0;
plog(XLOG_USER, "LDAP timestamp search failed: %s\n",
ldap_err2string(err));
if (res)
ldap_msgfree(res);
return (ENOENT);
}
nentries = ldap_count_entries(a->ldap, res);
if (nentries == 0) {
plog(XLOG_USER, "No timestamp entry for map %s\n", map);
*ts = 0;
ldap_msgfree(res);
return (ENOENT);
}
entry = ldap_first_entry(a->ldap, res);
vals = ldap_get_values(a->ldap, entry, AMD_LDAP_TSATTR);
if (ldap_count_values(vals) == 0) {
plog(XLOG_USER, "Missing timestamp value for map %s\n", map);
*ts = 0;
ldap_value_free(vals);
ldap_msgfree(res);
return (ENOENT);
}
dlog("TS value is:%s:\n", vals[0]);
if (vals[0]) {
*ts = (time_t) strtol(vals[0], &end, 10);
if (end == vals[0]) {
plog(XLOG_USER, "Unable to decode ldap timestamp %s for map %s\n",
vals[0], map);
err = ENOENT;
}
if (*ts <= 0) {
plog(XLOG_USER, "Nonpositive timestamp %ld for map %s\n",
(u_long) *ts, map);
err = ENOENT;
}
} else {
plog(XLOG_USER, "Empty timestamp value for map %s\n", map);
*ts = 0;
err = ENOENT;
}
ldap_value_free(vals);
ldap_msgfree(res);
dlog("The timestamp for %s is %ld (err=%d)\n", map, (u_long) *ts, err);
return (err);
}
int
amu_ldap_search(mnt_map *m, char *map, char *key, char **pval, time_t *ts)
{
char **vals, filter[MAXPATHLEN], filter2[2 * MAXPATHLEN];
char *f1, *f2;
struct timeval tv;
int i, err = 0, nvals = 0, nentries = 0;
LDAPMessage *entry, *res = NULL;
ALD *a = (ALD *) (m->map_data);
dlog("-> amu_ldap_search: map <%s>, key <%s>\n", map, key);
tv.tv_sec = 2;
tv.tv_usec = 0;
if (a == NULL) {
plog(XLOG_USER, "LDAP panic: no map data\n");
return (EIO);
}
if (amu_ldap_rebind(a)) /* Check that's the handle is still valid */
return (ENOENT);
xsnprintf(filter, sizeof(filter), AMD_LDAP_FILTER, map, key);
/* "*" is special to ldap_search(); run through the filter escaping it. */
f1 = filter; f2 = filter2;
while (*f1) {
if (*f1 == '*') {
*f2++ = '\\'; *f2++ = '2'; *f2++ = 'a';
f1++;
} else {
*f2++ = *f1++;
}
}
*f2 = '\0';
dlog("Search with filter: <%s>\n", filter2);
for (i = 0; i < AMD_LDAP_RETRIES; i++) {
err = ldap_search_st(a->ldap,
gopt.ldap_base,
LDAP_SCOPE_SUBTREE,
filter2,
0,
0,
&tv,
&res);
if (err == LDAP_SUCCESS)
break;
if (res) {
ldap_msgfree(res);
res = NULL;
}
plog(XLOG_USER, "LDAP search attempt %d failed: %s\n",
i + 1, ldap_err2string(err));
if (err != LDAP_TIMEOUT) {
dlog("amu_ldap_search: unbinding...\n");
amu_ldap_unbind(a->ldap);
a->ldap = NULL;
if (amu_ldap_rebind(a))
return (ENOENT);
}
}
switch (err) {
case LDAP_SUCCESS:
break;
case LDAP_NO_SUCH_OBJECT:
dlog("No object\n");
if (res)
ldap_msgfree(res);
return (ENOENT);
default:
plog(XLOG_USER, "LDAP search failed: %s\n",
ldap_err2string(err));
if (res)
ldap_msgfree(res);
return (EIO);
}
nentries = ldap_count_entries(a->ldap, res);
dlog("Search found %d entries\n", nentries);
if (nentries == 0) {
ldap_msgfree(res);
return (ENOENT);
}
entry = ldap_first_entry(a->ldap, res);
vals = ldap_get_values(a->ldap, entry, AMD_LDAP_ATTR);
nvals = ldap_count_values(vals);
if (nvals == 0) {
plog(XLOG_USER, "Missing value for %s in map %s\n", key, map);
ldap_value_free(vals);
ldap_msgfree(res);
return (EIO);
}
dlog("Map %s, %s => %s\n", map, key, vals[0]);
if (vals[0]) {
if (m->cfm && (m->cfm->cfm_flags & CFM_SUN_MAP_SYNTAX))
*pval = sun_entry2amd(key, vals[0]);
else
*pval = xstrdup(vals[0]);
err = 0;
} else {
plog(XLOG_USER, "Empty value for %s in map %s\n", key, map);
err = ENOENT;
}
ldap_msgfree(res);
ldap_value_free(vals);
return (err);
}
int
amu_ldap_mtime(mnt_map *m, char *map, time_t *ts)
{
ALD *aldh = (ALD *) (m->map_data);
if (aldh == NULL) {
dlog("LDAP panic: unable to find map data\n");
return (ENOENT);
}
if (amu_ldap_rebind(aldh)) {
return (ENOENT);
}
if (get_ldap_timestamp(aldh, map, ts)) {
return (ENOENT);
}
return (0);
}