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