1/* $NetBSD: info_ldap.c,v 1.1.1.2 2009/03/20 20:26:49 christos Exp $ */ 2 3/* 4 * Copyright (c) 1997-2009 Erez Zadok 5 * Copyright (c) 1989 Jan-Simon Pendry 6 * Copyright (c) 1989 Imperial College of Science, Technology & Medicine 7 * Copyright (c) 1989 The Regents of the University of California. 8 * All rights reserved. 9 * 10 * This code is derived from software contributed to Berkeley by 11 * Jan-Simon Pendry at Imperial College, London. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 3. All advertising materials mentioning features or use of this software 22 * must display the following acknowledgment: 23 * This product includes software developed by the University of 24 * California, Berkeley and its contributors. 25 * 4. Neither the name of the University nor the names of its contributors 26 * may be used to endorse or promote products derived from this software 27 * without specific prior written permission. 28 * 29 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 30 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 32 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 39 * SUCH DAMAGE. 40 * 41 * 42 * File: am-utils/amd/info_ldap.c 43 * 44 */ 45 46 47/* 48 * Get info from LDAP (Lightweight Directory Access Protocol) 49 * LDAP Home Page: http://www.umich.edu/~rsug/ldap/ 50 */ 51 52/* 53 * WARNING: as of Linux Fedora Core 5 (which comes with openldap-2.3.9), the 54 * ldap.h headers deprecate several functions used in this file, such as 55 * ldap_unbind. You get compile errors about missing extern definitions. 56 * Those externs are still in <ldap.h>, but surrounded by an ifdef 57 * LDAP_DEPRECATED. I am turning on that ifdef here, under the assumption 58 * that the functions may be deprecated, but they still work for this 59 * (older?) version of the LDAP API. It gets am-utils to compile, but it is 60 * not clear if it will work perfectly. 61 */ 62#ifndef LDAP_DEPRECATED 63# define LDAP_DEPRECATED 1 64#endif /* not LDAP_DEPRECATED */ 65 66#ifdef HAVE_CONFIG_H 67# include <config.h> 68#endif /* HAVE_CONFIG_H */ 69#include <am_defs.h> 70#include <amd.h> 71#include <sun_map.h> 72 73 74/* 75 * MACROS: 76 */ 77#define AMD_LDAP_TYPE "ldap" 78/* Time to live for an LDAP cached in an mnt_map */ 79#define AMD_LDAP_TTL 3600 80#define AMD_LDAP_RETRIES 5 81#define AMD_LDAP_HOST "ldap" 82#ifndef LDAP_PORT 83# define LDAP_PORT 389 84#endif /* LDAP_PORT */ 85 86/* How timestamps are searched */ 87#define AMD_LDAP_TSFILTER "(&(objectClass=amdmapTimestamp)(amdmapName=%s))" 88/* How maps are searched */ 89#define AMD_LDAP_FILTER "(&(objectClass=amdmap)(amdmapName=%s)(amdmapKey=%s))" 90/* How timestamps are stored */ 91#define AMD_LDAP_TSATTR "amdmaptimestamp" 92/* How maps are stored */ 93#define AMD_LDAP_ATTR "amdmapvalue" 94 95/* 96 * TYPEDEFS: 97 */ 98typedef struct ald_ent ALD; 99typedef struct cr_ent CR; 100typedef struct he_ent HE_ENT; 101 102/* 103 * STRUCTURES: 104 */ 105struct ald_ent { 106 LDAP *ldap; 107 HE_ENT *hostent; 108 CR *credentials; 109 time_t timestamp; 110}; 111 112struct cr_ent { 113 char *who; 114 char *pw; 115 int method; 116}; 117 118struct he_ent { 119 char *host; 120 int port; 121 struct he_ent *next; 122}; 123 124/* 125 * FORWARD DECLARATIONS: 126 */ 127static int amu_ldap_rebind(ALD *a); 128static int get_ldap_timestamp(ALD *a, char *map, time_t *ts); 129 130int amu_ldap_init(mnt_map *m, char *map, time_t *tsu); 131int amu_ldap_search(mnt_map *m, char *map, char *key, char **pval, time_t *ts); 132int amu_ldap_mtime(mnt_map *m, char *map, time_t *ts); 133 134/* 135 * FUNCTIONS: 136 */ 137 138static void 139he_free(HE_ENT *h) 140{ 141 XFREE(h->host); 142 if (h->next != NULL) 143 he_free(h->next); 144 XFREE(h); 145} 146 147 148static HE_ENT * 149string2he(char *s_orig) 150{ 151 char *c, *p; 152 char *s; 153 HE_ENT *first = NULL, *cur = NULL; 154 155 if (NULL == s_orig || NULL == (s = strdup(s_orig))) 156 return NULL; 157 for (p = strtok(s, ","); p; p = strtok(NULL, ",")) { 158 if (cur != NULL) { 159 cur->next = ALLOC(HE_ENT); 160 cur = cur->next; 161 } else 162 first = cur = ALLOC(HE_ENT); 163 164 cur->next = NULL; 165 c = strchr(p, ':'); 166 if (c) { /* Host and port */ 167 *c++ = '\0'; 168 cur->host = strdup(p); 169 cur->port = atoi(c); 170 } else { 171 cur->host = strdup(p); 172 cur->port = LDAP_PORT; 173 } 174 plog(XLOG_USER, "Adding ldap server %s:%d", 175 cur->host, cur->port); 176 } 177 XFREE(s); 178 return first; 179} 180 181 182static void 183cr_free(CR *c) 184{ 185 XFREE(c->who); 186 XFREE(c->pw); 187 XFREE(c); 188} 189 190 191/* 192 * Special ldap_unbind function to handle SIGPIPE. 193 * We first ignore SIGPIPE, in case a remote LDAP server was 194 * restarted, then we reinstall the handler. 195 */ 196static int 197amu_ldap_unbind(LDAP *ld) 198{ 199 int e; 200#ifdef HAVE_SIGACTION 201 struct sigaction sa; 202#else /* not HAVE_SIGACTION */ 203 void (*handler)(int); 204#endif /* not HAVE_SIGACTION */ 205 206 dlog("amu_ldap_unbind()\n"); 207 208#ifdef HAVE_SIGACTION 209 sa.sa_handler = SIG_IGN; 210 sa.sa_flags = 0; 211 sigemptyset(&(sa.sa_mask)); 212 sigaddset(&(sa.sa_mask), SIGPIPE); 213 sigaction(SIGPIPE, &sa, &sa); /* set IGNORE, and get old action */ 214#else /* not HAVE_SIGACTION */ 215 handler = signal(SIGPIPE, SIG_IGN); 216#endif /* not HAVE_SIGACTION */ 217 218 e = ldap_unbind(ld); 219 220#ifdef HAVE_SIGACTION 221 sigemptyset(&(sa.sa_mask)); 222 sigaddset(&(sa.sa_mask), SIGPIPE); 223 sigaction(SIGPIPE, &sa, NULL); 224#else /* not HAVE_SIGACTION */ 225 (void) signal(SIGPIPE, handler); 226#endif /* not HAVE_SIGACTION */ 227 228 return e; 229} 230 231 232static void 233ald_free(ALD *a) 234{ 235 he_free(a->hostent); 236 cr_free(a->credentials); 237 if (a->ldap != NULL) 238 amu_ldap_unbind(a->ldap); 239 XFREE(a); 240} 241 242 243int 244amu_ldap_init(mnt_map *m, char *map, time_t *ts) 245{ 246 ALD *aldh; 247 CR *creds; 248 249 dlog("-> amu_ldap_init: map <%s>\n", map); 250 251 /* 252 * XXX: by checking that map_type must be defined, aren't we 253 * excluding the possibility of automatic searches through all 254 * map types? 255 */ 256 if (!gopt.map_type || !STREQ(gopt.map_type, AMD_LDAP_TYPE)) { 257 dlog("amu_ldap_init called with map_type <%s>\n", 258 (gopt.map_type ? gopt.map_type : "null")); 259 } else { 260 dlog("Map %s is ldap\n", map); 261 } 262 263 aldh = ALLOC(ALD); 264 creds = ALLOC(CR); 265 aldh->ldap = NULL; 266 aldh->hostent = string2he(gopt.ldap_hostports); 267 if (aldh->hostent == NULL) { 268 plog(XLOG_USER, "Unable to parse hostport %s for ldap map %s", 269 gopt.ldap_hostports ? gopt.ldap_hostports : "(null)", map); 270 XFREE(creds); 271 XFREE(aldh); 272 return (ENOENT); 273 } 274 creds->who = ""; 275 creds->pw = ""; 276 creds->method = LDAP_AUTH_SIMPLE; 277 aldh->credentials = creds; 278 aldh->timestamp = 0; 279 aldh->ldap = NULL; 280 dlog("Trying for %s:%d\n", aldh->hostent->host, aldh->hostent->port); 281 if (amu_ldap_rebind(aldh)) { 282 ald_free(aldh); 283 return (ENOENT); 284 } 285 m->map_data = (void *) aldh; 286 dlog("Bound to %s:%d\n", aldh->hostent->host, aldh->hostent->port); 287 if (get_ldap_timestamp(aldh, map, ts)) 288 return (ENOENT); 289 dlog("Got timestamp for map %s: %ld\n", map, (u_long) *ts); 290 291 return (0); 292} 293 294 295static int 296amu_ldap_rebind(ALD *a) 297{ 298 LDAP *ld; 299 HE_ENT *h; 300 CR *c = a->credentials; 301 time_t now = clocktime(NULL); 302 int try; 303 304 dlog("-> amu_ldap_rebind\n"); 305 306 if (a->ldap != NULL) { 307 if ((a->timestamp - now) > AMD_LDAP_TTL) { 308 dlog("Re-establishing ldap connection\n"); 309 amu_ldap_unbind(a->ldap); 310 a->timestamp = now; 311 a->ldap = NULL; 312 } else { 313 /* Assume all is OK. If it wasn't we'll be back! */ 314 dlog("amu_ldap_rebind: timestamp OK\n"); 315 return (0); 316 } 317 } 318 319 for (try=0; try<10; try++) { /* XXX: try up to 10 times (makes sense?) */ 320 for (h = a->hostent; h != NULL; h = h->next) { 321 if ((ld = ldap_open(h->host, h->port)) == NULL) { 322 plog(XLOG_WARNING, "Unable to ldap_open to %s:%d\n", h->host, h->port); 323 continue; 324 } 325#if LDAP_VERSION_MAX > LDAP_VERSION2 326 /* handle LDAPv3 and heigher, if available and amd.conf-igured */ 327 if (gopt.ldap_proto_version > LDAP_VERSION2) { 328 if (!ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &gopt.ldap_proto_version)) { 329 dlog("amu_ldap_rebind: LDAP protocol version set to %ld\n", 330 gopt.ldap_proto_version); 331 } else { 332 plog(XLOG_WARNING, "Unable to set ldap protocol version to %ld for " 333 "%s:%d\n", gopt.ldap_proto_version, h->host, h->port); 334 continue; 335 } 336 } 337#endif /* LDAP_VERSION_MAX > LDAP_VERSION2 */ 338 if (ldap_bind_s(ld, c->who, c->pw, c->method) != LDAP_SUCCESS) { 339 plog(XLOG_WARNING, "Unable to ldap_bind to %s:%d as %s\n", 340 h->host, h->port, c->who); 341 continue; 342 } 343 if (gopt.ldap_cache_seconds > 0) { 344#if defined(HAVE_LDAP_ENABLE_CACHE) && defined(HAVE_EXTERN_LDAP_ENABLE_CACHE) 345 ldap_enable_cache(ld, gopt.ldap_cache_seconds, gopt.ldap_cache_maxmem); 346#else /* not defined(HAVE_LDAP_ENABLE_CACHE) && defined(HAVE_EXTERN_LDAP_ENABLE_CACHE) */ 347 plog(XLOG_WARNING, "ldap_enable_cache(%ld) is not available on this system!\n", gopt.ldap_cache_seconds); 348#endif /* not defined(HAVE_LDAP_ENABLE_CACHE) && defined(HAVE_EXTERN_LDAP_ENABLE_CACHE) */ 349 } 350 a->ldap = ld; 351 a->timestamp = now; 352 return (0); 353 } 354 plog(XLOG_WARNING, "Exhausted list of ldap servers, looping.\n"); 355 } 356 357 plog(XLOG_USER, "Unable to (re)bind to any ldap hosts\n"); 358 return (ENOENT); 359} 360 361 362static int 363get_ldap_timestamp(ALD *a, char *map, time_t *ts) 364{ 365 struct timeval tv; 366 char **vals, *end; 367 char filter[MAXPATHLEN]; 368 int i, err = 0, nentries = 0; 369 LDAPMessage *res = NULL, *entry; 370 371 dlog("-> get_ldap_timestamp: map <%s>\n", map); 372 373 tv.tv_sec = 3; 374 tv.tv_usec = 0; 375 xsnprintf(filter, sizeof(filter), AMD_LDAP_TSFILTER, map); 376 dlog("Getting timestamp for map %s\n", map); 377 dlog("Filter is: %s\n", filter); 378 dlog("Base is: %s\n", gopt.ldap_base); 379 for (i = 0; i < AMD_LDAP_RETRIES; i++) { 380 err = ldap_search_st(a->ldap, 381 gopt.ldap_base, 382 LDAP_SCOPE_SUBTREE, 383 filter, 384 0, 385 0, 386 &tv, 387 &res); 388 if (err == LDAP_SUCCESS) 389 break; 390 if (res) { 391 ldap_msgfree(res); 392 res = NULL; 393 } 394 plog(XLOG_USER, "Timestamp LDAP search attempt %d failed: %s\n", 395 i + 1, ldap_err2string(err)); 396 if (err != LDAP_TIMEOUT) { 397 dlog("get_ldap_timestamp: unbinding...\n"); 398 amu_ldap_unbind(a->ldap); 399 a->ldap = NULL; 400 if (amu_ldap_rebind(a)) 401 return (ENOENT); 402 } 403 dlog("Timestamp search failed, trying again...\n"); 404 } 405 406 if (err != LDAP_SUCCESS) { 407 *ts = 0; 408 plog(XLOG_USER, "LDAP timestamp search failed: %s\n", 409 ldap_err2string(err)); 410 if (res) 411 ldap_msgfree(res); 412 return (ENOENT); 413 } 414 415 nentries = ldap_count_entries(a->ldap, res); 416 if (nentries == 0) { 417 plog(XLOG_USER, "No timestamp entry for map %s\n", map); 418 *ts = 0; 419 ldap_msgfree(res); 420 return (ENOENT); 421 } 422 423 entry = ldap_first_entry(a->ldap, res); 424 vals = ldap_get_values(a->ldap, entry, AMD_LDAP_TSATTR); 425 if (ldap_count_values(vals) == 0) { 426 plog(XLOG_USER, "Missing timestamp value for map %s\n", map); 427 *ts = 0; 428 ldap_value_free(vals); 429 ldap_msgfree(res); 430 return (ENOENT); 431 } 432 dlog("TS value is:%s:\n", vals[0]); 433 434 if (vals[0]) { 435 *ts = (time_t) strtol(vals[0], &end, 10); 436 if (end == vals[0]) { 437 plog(XLOG_USER, "Unable to decode ldap timestamp %s for map %s\n", 438 vals[0], map); 439 err = ENOENT; 440 } 441 if (!*ts > 0) { 442 plog(XLOG_USER, "Nonpositive timestamp %ld for map %s\n", 443 (u_long) *ts, map); 444 err = ENOENT; 445 } 446 } else { 447 plog(XLOG_USER, "Empty timestamp value for map %s\n", map); 448 *ts = 0; 449 err = ENOENT; 450 } 451 452 ldap_value_free(vals); 453 ldap_msgfree(res); 454 dlog("The timestamp for %s is %ld (err=%d)\n", map, (u_long) *ts, err); 455 return (err); 456} 457 458 459int 460amu_ldap_search(mnt_map *m, char *map, char *key, char **pval, time_t *ts) 461{ 462 char **vals, filter[MAXPATHLEN], filter2[2 * MAXPATHLEN]; 463 char *f1, *f2; 464 struct timeval tv; 465 int i, err = 0, nvals = 0, nentries = 0; 466 LDAPMessage *entry, *res = NULL; 467 ALD *a = (ALD *) (m->map_data); 468 469 dlog("-> amu_ldap_search: map <%s>, key <%s>\n", map, key); 470 471 tv.tv_sec = 2; 472 tv.tv_usec = 0; 473 if (a == NULL) { 474 plog(XLOG_USER, "LDAP panic: no map data\n"); 475 return (EIO); 476 } 477 if (amu_ldap_rebind(a)) /* Check that's the handle is still valid */ 478 return (ENOENT); 479 480 xsnprintf(filter, sizeof(filter), AMD_LDAP_FILTER, map, key); 481 /* "*" is special to ldap_search(); run through the filter escaping it. */ 482 f1 = filter; f2 = filter2; 483 while (*f1) { 484 if (*f1 == '*') { 485 *f2++ = '\\'; *f2++ = '2'; *f2++ = 'a'; 486 f1++; 487 } else { 488 *f2++ = *f1++; 489 } 490 } 491 *f2 = '\0'; 492 dlog("Search with filter: <%s>\n", filter2); 493 for (i = 0; i < AMD_LDAP_RETRIES; i++) { 494 err = ldap_search_st(a->ldap, 495 gopt.ldap_base, 496 LDAP_SCOPE_SUBTREE, 497 filter2, 498 0, 499 0, 500 &tv, 501 &res); 502 if (err == LDAP_SUCCESS) 503 break; 504 if (res) { 505 ldap_msgfree(res); 506 res = NULL; 507 } 508 plog(XLOG_USER, "LDAP search attempt %d failed: %s\n", 509 i + 1, ldap_err2string(err)); 510 if (err != LDAP_TIMEOUT) { 511 dlog("amu_ldap_search: unbinding...\n"); 512 amu_ldap_unbind(a->ldap); 513 a->ldap = NULL; 514 if (amu_ldap_rebind(a)) 515 return (ENOENT); 516 } 517 } 518 519 switch (err) { 520 case LDAP_SUCCESS: 521 break; 522 case LDAP_NO_SUCH_OBJECT: 523 dlog("No object\n"); 524 if (res) 525 ldap_msgfree(res); 526 return (ENOENT); 527 default: 528 plog(XLOG_USER, "LDAP search failed: %s\n", 529 ldap_err2string(err)); 530 if (res) 531 ldap_msgfree(res); 532 return (EIO); 533 } 534 535 nentries = ldap_count_entries(a->ldap, res); 536 dlog("Search found %d entries\n", nentries); 537 if (nentries == 0) { 538 ldap_msgfree(res); 539 return (ENOENT); 540 } 541 entry = ldap_first_entry(a->ldap, res); 542 vals = ldap_get_values(a->ldap, entry, AMD_LDAP_ATTR); 543 nvals = ldap_count_values(vals); 544 if (nvals == 0) { 545 plog(XLOG_USER, "Missing value for %s in map %s\n", key, map); 546 ldap_value_free(vals); 547 ldap_msgfree(res); 548 return (EIO); 549 } 550 dlog("Map %s, %s => %s\n", map, key, vals[0]); 551 if (vals[0]) { 552 if (m->cfm && (m->cfm->cfm_flags & CFM_SUN_MAP_SYNTAX)) 553 *pval = sun_entry2amd(key, vals[0]); 554 else 555 *pval = strdup(vals[0]); 556 err = 0; 557 } else { 558 plog(XLOG_USER, "Empty value for %s in map %s\n", key, map); 559 err = ENOENT; 560 } 561 ldap_msgfree(res); 562 ldap_value_free(vals); 563 564 return (err); 565} 566 567 568int 569amu_ldap_mtime(mnt_map *m, char *map, time_t *ts) 570{ 571 ALD *aldh = (ALD *) (m->map_data); 572 573 if (aldh == NULL) { 574 dlog("LDAP panic: unable to find map data\n"); 575 return (ENOENT); 576 } 577 if (amu_ldap_rebind(aldh)) { 578 return (ENOENT); 579 } 580 if (get_ldap_timestamp(aldh, map, ts)) { 581 return (ENOENT); 582 } 583 return (0); 584} 585