1 /* $NetBSD: info_ldap.c,v 1.1.1.1 2008/09/19 20:07:16 christos Exp $ */ 2 3 /* 4 * Copyright (c) 1997-2007 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 */ 98 typedef struct ald_ent ALD; 99 typedef struct cr_ent CR; 100 typedef struct he_ent HE_ENT; 101 102 /* 103 * STRUCTURES: 104 */ 105 struct ald_ent { 106 LDAP *ldap; 107 HE_ENT *hostent; 108 CR *credentials; 109 time_t timestamp; 110 }; 111 112 struct cr_ent { 113 char *who; 114 char *pw; 115 int method; 116 }; 117 118 struct he_ent { 119 char *host; 120 int port; 121 struct he_ent *next; 122 }; 123 124 /* 125 * FORWARD DECLARATIONS: 126 */ 127 static int amu_ldap_rebind(ALD *a); 128 static int get_ldap_timestamp(ALD *a, char *map, time_t *ts); 129 130 int amu_ldap_init(mnt_map *m, char *map, time_t *tsu); 131 int amu_ldap_search(mnt_map *m, char *map, char *key, char **pval, time_t *ts); 132 int amu_ldap_mtime(mnt_map *m, char *map, time_t *ts); 133 134 /* 135 * FUNCTIONS: 136 */ 137 138 static void 139 he_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 148 static HE_ENT * 149 string2he(char *s_orig) 150 { 151 char *c, *p; 152 char *s; 153 HE_ENT *new, *old = NULL; 154 155 if (NULL == s_orig || NULL == (s = strdup(s_orig))) 156 return NULL; 157 for (p = s; p; p = strchr(p, ',')) { 158 if (old != NULL) { 159 new = ALLOC(HE_ENT); 160 old->next = new; 161 old = new; 162 } else { 163 old = ALLOC(HE_ENT); 164 old->next = NULL; 165 } 166 c = strchr(p, ':'); 167 if (c) { /* Host and port */ 168 *c++ = '\0'; 169 old->host = strdup(p); 170 old->port = atoi(c); 171 } else 172 old->host = strdup(p); 173 174 } 175 XFREE(s); 176 return (old); 177 } 178 179 180 static void 181 cr_free(CR *c) 182 { 183 XFREE(c->who); 184 XFREE(c->pw); 185 XFREE(c); 186 } 187 188 189 /* 190 * Special ldap_unbind function to handle SIGPIPE. 191 * We first ignore SIGPIPE, in case a remote LDAP server was 192 * restarted, then we reinstall the handler. 193 */ 194 static int 195 amu_ldap_unbind(LDAP *ld) 196 { 197 int e; 198 #ifdef HAVE_SIGACTION 199 struct sigaction sa; 200 #else /* not HAVE_SIGACTION */ 201 void (*handler)(int); 202 #endif /* not HAVE_SIGACTION */ 203 204 dlog("amu_ldap_unbind()\n"); 205 206 #ifdef HAVE_SIGACTION 207 sa.sa_handler = SIG_IGN; 208 sa.sa_flags = 0; 209 sigemptyset(&(sa.sa_mask)); 210 sigaddset(&(sa.sa_mask), SIGPIPE); 211 sigaction(SIGPIPE, &sa, &sa); /* set IGNORE, and get old action */ 212 #else /* not HAVE_SIGACTION */ 213 handler = signal(SIGPIPE, SIG_IGN); 214 #endif /* not HAVE_SIGACTION */ 215 216 e = ldap_unbind(ld); 217 218 #ifdef HAVE_SIGACTION 219 sigemptyset(&(sa.sa_mask)); 220 sigaddset(&(sa.sa_mask), SIGPIPE); 221 sigaction(SIGPIPE, &sa, NULL); 222 #else /* not HAVE_SIGACTION */ 223 (void) signal(SIGPIPE, handler); 224 #endif /* not HAVE_SIGACTION */ 225 226 return e; 227 } 228 229 230 static void 231 ald_free(ALD *a) 232 { 233 he_free(a->hostent); 234 cr_free(a->credentials); 235 if (a->ldap != NULL) 236 amu_ldap_unbind(a->ldap); 237 XFREE(a); 238 } 239 240 241 int 242 amu_ldap_init(mnt_map *m, char *map, time_t *ts) 243 { 244 ALD *aldh; 245 CR *creds; 246 247 dlog("-> amu_ldap_init: map <%s>\n", map); 248 249 /* 250 * XXX: by checking that map_type must be defined, aren't we 251 * excluding the possibility of automatic searches through all 252 * map types? 253 */ 254 if (!gopt.map_type || !STREQ(gopt.map_type, AMD_LDAP_TYPE)) { 255 dlog("amu_ldap_init called with map_type <%s>\n", 256 (gopt.map_type ? gopt.map_type : "null")); 257 } else { 258 dlog("Map %s is ldap\n", map); 259 } 260 261 aldh = ALLOC(ALD); 262 creds = ALLOC(CR); 263 aldh->ldap = NULL; 264 aldh->hostent = string2he(gopt.ldap_hostports); 265 if (aldh->hostent == NULL) { 266 plog(XLOG_USER, "Unable to parse hostport %s for ldap map %s", 267 gopt.ldap_hostports ? gopt.ldap_hostports : "(null)", map); 268 XFREE(creds); 269 XFREE(aldh); 270 return (ENOENT); 271 } 272 creds->who = ""; 273 creds->pw = ""; 274 creds->method = LDAP_AUTH_SIMPLE; 275 aldh->credentials = creds; 276 aldh->timestamp = 0; 277 aldh->ldap = NULL; 278 dlog("Trying for %s:%d\n", aldh->hostent->host, aldh->hostent->port); 279 if (amu_ldap_rebind(aldh)) { 280 ald_free(aldh); 281 return (ENOENT); 282 } 283 m->map_data = (void *) aldh; 284 dlog("Bound to %s:%d\n", aldh->hostent->host, aldh->hostent->port); 285 if (get_ldap_timestamp(aldh, map, ts)) 286 return (ENOENT); 287 dlog("Got timestamp for map %s: %ld\n", map, (u_long) *ts); 288 289 return (0); 290 } 291 292 293 static int 294 amu_ldap_rebind(ALD *a) 295 { 296 LDAP *ld; 297 HE_ENT *h; 298 CR *c = a->credentials; 299 time_t now = clocktime(NULL); 300 int try; 301 302 dlog("-> amu_ldap_rebind\n"); 303 304 if (a->ldap != NULL) { 305 if ((a->timestamp - now) > AMD_LDAP_TTL) { 306 dlog("Re-establishing ldap connection\n"); 307 amu_ldap_unbind(a->ldap); 308 a->timestamp = now; 309 a->ldap = NULL; 310 } else { 311 /* Assume all is OK. If it wasn't we'll be back! */ 312 dlog("amu_ldap_rebind: timestamp OK\n"); 313 return (0); 314 } 315 } 316 317 for (try=0; try<10; try++) { /* XXX: try up to 10 times (makes sense?) */ 318 for (h = a->hostent; h != NULL; h = h->next) { 319 if ((ld = ldap_open(h->host, h->port)) == NULL) { 320 plog(XLOG_WARNING, "Unable to ldap_open to %s:%d\n", h->host, h->port); 321 break; 322 } 323 #if LDAP_VERSION_MAX > LDAP_VERSION2 324 /* handle LDAPv3 and heigher, if available and amd.conf-igured */ 325 if (gopt.ldap_proto_version > LDAP_VERSION2) { 326 if (!ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &gopt.ldap_proto_version)) { 327 dlog("amu_ldap_rebind: LDAP protocol version set to %ld\n", 328 gopt.ldap_proto_version); 329 } else { 330 plog(XLOG_WARNING, "Unable to set ldap protocol version to %ld\n", 331 gopt.ldap_proto_version); 332 break; 333 } 334 } 335 #endif /* LDAP_VERSION_MAX > LDAP_VERSION2 */ 336 if (ldap_bind_s(ld, c->who, c->pw, c->method) != LDAP_SUCCESS) { 337 plog(XLOG_WARNING, "Unable to ldap_bind to %s:%d as %s\n", 338 h->host, h->port, c->who); 339 break; 340 } 341 if (gopt.ldap_cache_seconds > 0) { 342 #if defined(HAVE_LDAP_ENABLE_CACHE) && defined(HAVE_EXTERN_LDAP_ENABLE_CACHE) 343 ldap_enable_cache(ld, gopt.ldap_cache_seconds, gopt.ldap_cache_maxmem); 344 #else /* not defined(HAVE_LDAP_ENABLE_CACHE) && defined(HAVE_EXTERN_LDAP_ENABLE_CACHE) */ 345 plog(XLOG_WARNING, "ldap_enable_cache(%ld) is not available on this system!\n", gopt.ldap_cache_seconds); 346 #endif /* not defined(HAVE_LDAP_ENABLE_CACHE) && defined(HAVE_EXTERN_LDAP_ENABLE_CACHE) */ 347 } 348 a->ldap = ld; 349 a->timestamp = now; 350 return (0); 351 } 352 plog(XLOG_WARNING, "Exhausted list of ldap servers, looping.\n"); 353 } 354 355 plog(XLOG_USER, "Unable to (re)bind to any ldap hosts\n"); 356 return (ENOENT); 357 } 358 359 360 static int 361 get_ldap_timestamp(ALD *a, char *map, time_t *ts) 362 { 363 struct timeval tv; 364 char **vals, *end; 365 char filter[MAXPATHLEN]; 366 int i, err = 0, nentries = 0; 367 LDAPMessage *res = NULL, *entry; 368 369 dlog("-> get_ldap_timestamp: map <%s>\n", map); 370 371 tv.tv_sec = 3; 372 tv.tv_usec = 0; 373 xsnprintf(filter, sizeof(filter), AMD_LDAP_TSFILTER, map); 374 dlog("Getting timestamp for map %s\n", map); 375 dlog("Filter is: %s\n", filter); 376 dlog("Base is: %s\n", gopt.ldap_base); 377 for (i = 0; i < AMD_LDAP_RETRIES; i++) { 378 err = ldap_search_st(a->ldap, 379 gopt.ldap_base, 380 LDAP_SCOPE_SUBTREE, 381 filter, 382 0, 383 0, 384 &tv, 385 &res); 386 if (err == LDAP_SUCCESS) 387 break; 388 if (res) { 389 ldap_msgfree(res); 390 res = NULL; 391 } 392 plog(XLOG_USER, "Timestamp LDAP search attempt %d failed: %s\n", 393 i + 1, ldap_err2string(err)); 394 if (err != LDAP_TIMEOUT) { 395 dlog("get_ldap_timestamp: unbinding...\n"); 396 amu_ldap_unbind(a->ldap); 397 a->ldap = NULL; 398 if (amu_ldap_rebind(a)) 399 return (ENOENT); 400 } 401 dlog("Timestamp search failed, trying again...\n"); 402 } 403 404 if (err != LDAP_SUCCESS) { 405 *ts = 0; 406 plog(XLOG_USER, "LDAP timestamp search failed: %s\n", 407 ldap_err2string(err)); 408 if (res) 409 ldap_msgfree(res); 410 return (ENOENT); 411 } 412 413 nentries = ldap_count_entries(a->ldap, res); 414 if (nentries == 0) { 415 plog(XLOG_USER, "No timestamp entry for map %s\n", map); 416 *ts = 0; 417 ldap_msgfree(res); 418 return (ENOENT); 419 } 420 421 entry = ldap_first_entry(a->ldap, res); 422 vals = ldap_get_values(a->ldap, entry, AMD_LDAP_TSATTR); 423 if (ldap_count_values(vals) == 0) { 424 plog(XLOG_USER, "Missing timestamp value for map %s\n", map); 425 *ts = 0; 426 ldap_value_free(vals); 427 ldap_msgfree(res); 428 return (ENOENT); 429 } 430 dlog("TS value is:%s:\n", vals[0]); 431 432 if (vals[0]) { 433 *ts = (time_t) strtol(vals[0], &end, 10); 434 if (end == vals[0]) { 435 plog(XLOG_USER, "Unable to decode ldap timestamp %s for map %s\n", 436 vals[0], map); 437 err = ENOENT; 438 } 439 if (!*ts > 0) { 440 plog(XLOG_USER, "Nonpositive timestamp %ld for map %s\n", 441 (u_long) *ts, map); 442 err = ENOENT; 443 } 444 } else { 445 plog(XLOG_USER, "Empty timestamp value for map %s\n", map); 446 *ts = 0; 447 err = ENOENT; 448 } 449 450 ldap_value_free(vals); 451 ldap_msgfree(res); 452 dlog("The timestamp for %s is %ld (err=%d)\n", map, (u_long) *ts, err); 453 return (err); 454 } 455 456 457 int 458 amu_ldap_search(mnt_map *m, char *map, char *key, char **pval, time_t *ts) 459 { 460 char **vals, filter[MAXPATHLEN], filter2[2 * MAXPATHLEN]; 461 char *f1, *f2; 462 struct timeval tv; 463 int i, err = 0, nvals = 0, nentries = 0; 464 LDAPMessage *entry, *res = NULL; 465 ALD *a = (ALD *) (m->map_data); 466 467 dlog("-> amu_ldap_search: map <%s>, key <%s>\n", map, key); 468 469 tv.tv_sec = 2; 470 tv.tv_usec = 0; 471 if (a == NULL) { 472 plog(XLOG_USER, "LDAP panic: no map data\n"); 473 return (EIO); 474 } 475 if (amu_ldap_rebind(a)) /* Check that's the handle is still valid */ 476 return (ENOENT); 477 478 xsnprintf(filter, sizeof(filter), AMD_LDAP_FILTER, map, key); 479 /* "*" is special to ldap_search(); run through the filter escaping it. */ 480 f1 = filter; f2 = filter2; 481 while (*f1) { 482 if (*f1 == '*') { 483 *f2++ = '\\'; *f2++ = '2'; *f2++ = 'a'; 484 f1++; 485 } else { 486 *f2++ = *f1++; 487 } 488 } 489 *f2 = '\0'; 490 dlog("Search with filter: <%s>\n", filter2); 491 for (i = 0; i < AMD_LDAP_RETRIES; i++) { 492 err = ldap_search_st(a->ldap, 493 gopt.ldap_base, 494 LDAP_SCOPE_SUBTREE, 495 filter2, 496 0, 497 0, 498 &tv, 499 &res); 500 if (err == LDAP_SUCCESS) 501 break; 502 if (res) { 503 ldap_msgfree(res); 504 res = NULL; 505 } 506 plog(XLOG_USER, "LDAP search attempt %d failed: %s\n", 507 i + 1, ldap_err2string(err)); 508 if (err != LDAP_TIMEOUT) { 509 dlog("amu_ldap_search: unbinding...\n"); 510 amu_ldap_unbind(a->ldap); 511 a->ldap = NULL; 512 if (amu_ldap_rebind(a)) 513 return (ENOENT); 514 } 515 } 516 517 switch (err) { 518 case LDAP_SUCCESS: 519 break; 520 case LDAP_NO_SUCH_OBJECT: 521 dlog("No object\n"); 522 if (res) 523 ldap_msgfree(res); 524 return (ENOENT); 525 default: 526 plog(XLOG_USER, "LDAP search failed: %s\n", 527 ldap_err2string(err)); 528 if (res) 529 ldap_msgfree(res); 530 return (EIO); 531 } 532 533 nentries = ldap_count_entries(a->ldap, res); 534 dlog("Search found %d entries\n", nentries); 535 if (nentries == 0) { 536 ldap_msgfree(res); 537 return (ENOENT); 538 } 539 entry = ldap_first_entry(a->ldap, res); 540 vals = ldap_get_values(a->ldap, entry, AMD_LDAP_ATTR); 541 nvals = ldap_count_values(vals); 542 if (nvals == 0) { 543 plog(XLOG_USER, "Missing value for %s in map %s\n", key, map); 544 ldap_value_free(vals); 545 ldap_msgfree(res); 546 return (EIO); 547 } 548 dlog("Map %s, %s => %s\n", map, key, vals[0]); 549 if (vals[0]) { 550 if (m->cfm && (m->cfm->cfm_flags & CFM_SUN_MAP_SYNTAX)) 551 *pval = sun_entry2amd(key, vals[0]); 552 else 553 *pval = strdup(vals[0]); 554 err = 0; 555 } else { 556 plog(XLOG_USER, "Empty value for %s in map %s\n", key, map); 557 err = ENOENT; 558 } 559 ldap_msgfree(res); 560 ldap_value_free(vals); 561 562 return (err); 563 } 564 565 566 int 567 amu_ldap_mtime(mnt_map *m, char *map, time_t *ts) 568 { 569 ALD *aldh = (ALD *) (m->map_data); 570 571 if (aldh == NULL) { 572 dlog("LDAP panic: unable to find map data\n"); 573 return (ENOENT); 574 } 575 if (amu_ldap_rebind(aldh)) { 576 return (ENOENT); 577 } 578 if (get_ldap_timestamp(aldh, map, ts)) { 579 return (ENOENT); 580 } 581 return (0); 582 } 583