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