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
he_free(HE_ENT * h)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 *
string2he(char * s_orig)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
cr_free(CR * c)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
amu_ldap_unbind(LDAP * ld)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
ald_free(ALD * a)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
amu_ldap_init(mnt_map * m,char * map,time_t * ts)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
amu_ldap_rebind(ALD * a)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
get_ldap_timestamp(ALD * a,char * map,time_t * ts)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
amu_ldap_search(mnt_map * m,char * map,char * key,char ** pval,time_t * ts)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
amu_ldap_mtime(mnt_map * m,char * map,time_t * ts)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