/* $NetBSD: info_ldap.c,v 1.6 2006/02/05 16:28:56 christos Exp $ */ /* * Copyright (c) 1997-2005 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. All advertising materials mentioning features or use of this software * must display the following acknowledgment: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. 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/ */ #ifdef HAVE_CONFIG_H # include #endif /* HAVE_CONFIG_H */ #include #include /* * 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; }; /* * FORWARD DECLARATIONS: */ static int amu_ldap_rebind(ALD *a); static int get_ldap_timestamp(ALD *a, 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 *new, *old = NULL; if (NULL == s_orig || NULL == (s = strdup(s_orig))) return NULL; for (p = s; p; p = strchr(p, ',')) { if (old != NULL) { new = ALLOC(HE_ENT); old->next = new; old = new; } else { old = ALLOC(HE_ENT); old->next = NULL; } c = strchr(p, ':'); if (c) { /* Host and port */ *c++ = '\0'; old->host = strdup(p); old->port = atoi(c); } else old->host = strdup(p); } XFREE(s); return (old); } 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")); } else { dlog("Map %s is ldap\n", map); } 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); } m->map_data = (void *) aldh; dlog("Bound to %s:%d\n", aldh->hostent->host, aldh->hostent->port); if (get_ldap_timestamp(aldh, map, ts)) return (ENOENT); dlog("Got timestamp for map %s: %ld\n", map, (u_long) *ts); 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); break; } #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\n", gopt.ldap_proto_version); break; } } #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); break; } 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]) { *pval = strdup(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); }