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