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