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