xref: /netbsd-src/external/bsd/am-utils/dist/amd/info_ldap.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /*	$NetBSD: info_ldap.c,v 1.1.1.2 2009/03/20 20:26:49 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1997-2009 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 *first = NULL, *cur = NULL;
154 
155   if (NULL == s_orig || NULL == (s = strdup(s_orig)))
156     return NULL;
157   for (p = strtok(s, ","); p; p = strtok(NULL, ",")) {
158     if (cur != NULL) {
159       cur->next = ALLOC(HE_ENT);
160       cur = cur->next;
161     } else
162       first = cur = ALLOC(HE_ENT);
163 
164     cur->next = NULL;
165     c = strchr(p, ':');
166     if (c) {            /* Host and port */
167       *c++ = '\0';
168       cur->host = strdup(p);
169       cur->port = atoi(c);
170     } else {
171       cur->host = strdup(p);
172       cur->port = LDAP_PORT;
173     }
174     plog(XLOG_USER, "Adding ldap server %s:%d",
175       cur->host, cur->port);
176   }
177   XFREE(s);
178   return first;
179 }
180 
181 
182 static void
183 cr_free(CR *c)
184 {
185   XFREE(c->who);
186   XFREE(c->pw);
187   XFREE(c);
188 }
189 
190 
191 /*
192  * Special ldap_unbind function to handle SIGPIPE.
193  * We first ignore SIGPIPE, in case a remote LDAP server was
194  * restarted, then we reinstall the handler.
195  */
196 static int
197 amu_ldap_unbind(LDAP *ld)
198 {
199   int e;
200 #ifdef HAVE_SIGACTION
201   struct sigaction sa;
202 #else /* not HAVE_SIGACTION */
203   void (*handler)(int);
204 #endif /* not HAVE_SIGACTION */
205 
206   dlog("amu_ldap_unbind()\n");
207 
208 #ifdef HAVE_SIGACTION
209   sa.sa_handler = SIG_IGN;
210   sa.sa_flags = 0;
211   sigemptyset(&(sa.sa_mask));
212   sigaddset(&(sa.sa_mask), SIGPIPE);
213   sigaction(SIGPIPE, &sa, &sa);	/* set IGNORE, and get old action */
214 #else /* not HAVE_SIGACTION */
215   handler = signal(SIGPIPE, SIG_IGN);
216 #endif /* not HAVE_SIGACTION */
217 
218   e = ldap_unbind(ld);
219 
220 #ifdef HAVE_SIGACTION
221   sigemptyset(&(sa.sa_mask));
222   sigaddset(&(sa.sa_mask), SIGPIPE);
223   sigaction(SIGPIPE, &sa, NULL);
224 #else /* not HAVE_SIGACTION */
225   (void) signal(SIGPIPE, handler);
226 #endif /* not HAVE_SIGACTION */
227 
228   return e;
229 }
230 
231 
232 static void
233 ald_free(ALD *a)
234 {
235   he_free(a->hostent);
236   cr_free(a->credentials);
237   if (a->ldap != NULL)
238     amu_ldap_unbind(a->ldap);
239   XFREE(a);
240 }
241 
242 
243 int
244 amu_ldap_init(mnt_map *m, char *map, time_t *ts)
245 {
246   ALD *aldh;
247   CR *creds;
248 
249   dlog("-> amu_ldap_init: map <%s>\n", map);
250 
251   /*
252    * XXX: by checking that map_type must be defined, aren't we
253    * excluding the possibility of automatic searches through all
254    * map types?
255    */
256   if (!gopt.map_type || !STREQ(gopt.map_type, AMD_LDAP_TYPE)) {
257     dlog("amu_ldap_init called with map_type <%s>\n",
258 	 (gopt.map_type ? gopt.map_type : "null"));
259   } else {
260     dlog("Map %s is ldap\n", map);
261   }
262 
263   aldh = ALLOC(ALD);
264   creds = ALLOC(CR);
265   aldh->ldap = NULL;
266   aldh->hostent = string2he(gopt.ldap_hostports);
267   if (aldh->hostent == NULL) {
268     plog(XLOG_USER, "Unable to parse hostport %s for ldap map %s",
269 	 gopt.ldap_hostports ? gopt.ldap_hostports : "(null)", map);
270     XFREE(creds);
271     XFREE(aldh);
272     return (ENOENT);
273   }
274   creds->who = "";
275   creds->pw = "";
276   creds->method = LDAP_AUTH_SIMPLE;
277   aldh->credentials = creds;
278   aldh->timestamp = 0;
279   aldh->ldap = NULL;
280   dlog("Trying for %s:%d\n", aldh->hostent->host, aldh->hostent->port);
281   if (amu_ldap_rebind(aldh)) {
282     ald_free(aldh);
283     return (ENOENT);
284   }
285   m->map_data = (void *) aldh;
286   dlog("Bound to %s:%d\n", aldh->hostent->host, aldh->hostent->port);
287   if (get_ldap_timestamp(aldh, map, ts))
288     return (ENOENT);
289   dlog("Got timestamp for map %s: %ld\n", map, (u_long) *ts);
290 
291   return (0);
292 }
293 
294 
295 static int
296 amu_ldap_rebind(ALD *a)
297 {
298   LDAP *ld;
299   HE_ENT *h;
300   CR *c = a->credentials;
301   time_t now = clocktime(NULL);
302   int try;
303 
304   dlog("-> amu_ldap_rebind\n");
305 
306   if (a->ldap != NULL) {
307     if ((a->timestamp - now) > AMD_LDAP_TTL) {
308       dlog("Re-establishing ldap connection\n");
309       amu_ldap_unbind(a->ldap);
310       a->timestamp = now;
311       a->ldap = NULL;
312     } else {
313       /* Assume all is OK.  If it wasn't we'll be back! */
314       dlog("amu_ldap_rebind: timestamp OK\n");
315       return (0);
316     }
317   }
318 
319   for (try=0; try<10; try++) {	/* XXX: try up to 10 times (makes sense?) */
320     for (h = a->hostent; h != NULL; h = h->next) {
321       if ((ld = ldap_open(h->host, h->port)) == NULL) {
322 	plog(XLOG_WARNING, "Unable to ldap_open to %s:%d\n", h->host, h->port);
323 	continue;
324       }
325 #if LDAP_VERSION_MAX > LDAP_VERSION2
326       /* handle LDAPv3 and heigher, if available and amd.conf-igured */
327       if (gopt.ldap_proto_version > LDAP_VERSION2) {
328         if (!ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &gopt.ldap_proto_version)) {
329           dlog("amu_ldap_rebind: LDAP protocol version set to %ld\n",
330 	       gopt.ldap_proto_version);
331         } else {
332           plog(XLOG_WARNING, "Unable to set ldap protocol version to %ld for "
333 	       "%s:%d\n", gopt.ldap_proto_version, h->host, h->port);
334 	  continue;
335         }
336       }
337 #endif /* LDAP_VERSION_MAX > LDAP_VERSION2 */
338       if (ldap_bind_s(ld, c->who, c->pw, c->method) != LDAP_SUCCESS) {
339 	plog(XLOG_WARNING, "Unable to ldap_bind to %s:%d as %s\n",
340 	     h->host, h->port, c->who);
341 	continue;
342       }
343       if (gopt.ldap_cache_seconds > 0) {
344 #if defined(HAVE_LDAP_ENABLE_CACHE) && defined(HAVE_EXTERN_LDAP_ENABLE_CACHE)
345 	ldap_enable_cache(ld, gopt.ldap_cache_seconds, gopt.ldap_cache_maxmem);
346 #else /* not defined(HAVE_LDAP_ENABLE_CACHE) && defined(HAVE_EXTERN_LDAP_ENABLE_CACHE) */
347 	plog(XLOG_WARNING, "ldap_enable_cache(%ld) is not available on this system!\n", gopt.ldap_cache_seconds);
348 #endif /* not defined(HAVE_LDAP_ENABLE_CACHE) && defined(HAVE_EXTERN_LDAP_ENABLE_CACHE) */
349       }
350       a->ldap = ld;
351       a->timestamp = now;
352       return (0);
353     }
354     plog(XLOG_WARNING, "Exhausted list of ldap servers, looping.\n");
355   }
356 
357   plog(XLOG_USER, "Unable to (re)bind to any ldap hosts\n");
358   return (ENOENT);
359 }
360 
361 
362 static int
363 get_ldap_timestamp(ALD *a, char *map, time_t *ts)
364 {
365   struct timeval tv;
366   char **vals, *end;
367   char filter[MAXPATHLEN];
368   int i, err = 0, nentries = 0;
369   LDAPMessage *res = NULL, *entry;
370 
371   dlog("-> get_ldap_timestamp: map <%s>\n", map);
372 
373   tv.tv_sec = 3;
374   tv.tv_usec = 0;
375   xsnprintf(filter, sizeof(filter), AMD_LDAP_TSFILTER, map);
376   dlog("Getting timestamp for map %s\n", map);
377   dlog("Filter is: %s\n", filter);
378   dlog("Base is: %s\n", gopt.ldap_base);
379   for (i = 0; i < AMD_LDAP_RETRIES; i++) {
380     err = ldap_search_st(a->ldap,
381 			 gopt.ldap_base,
382 			 LDAP_SCOPE_SUBTREE,
383 			 filter,
384 			 0,
385 			 0,
386 			 &tv,
387 			 &res);
388     if (err == LDAP_SUCCESS)
389       break;
390     if (res) {
391       ldap_msgfree(res);
392       res = NULL;
393     }
394     plog(XLOG_USER, "Timestamp LDAP search attempt %d failed: %s\n",
395 	 i + 1, ldap_err2string(err));
396     if (err != LDAP_TIMEOUT) {
397       dlog("get_ldap_timestamp: unbinding...\n");
398       amu_ldap_unbind(a->ldap);
399       a->ldap = NULL;
400       if (amu_ldap_rebind(a))
401         return (ENOENT);
402     }
403     dlog("Timestamp search failed, trying again...\n");
404   }
405 
406   if (err != LDAP_SUCCESS) {
407     *ts = 0;
408     plog(XLOG_USER, "LDAP timestamp search failed: %s\n",
409 	 ldap_err2string(err));
410     if (res)
411       ldap_msgfree(res);
412     return (ENOENT);
413   }
414 
415   nentries = ldap_count_entries(a->ldap, res);
416   if (nentries == 0) {
417     plog(XLOG_USER, "No timestamp entry for map %s\n", map);
418     *ts = 0;
419     ldap_msgfree(res);
420     return (ENOENT);
421   }
422 
423   entry = ldap_first_entry(a->ldap, res);
424   vals = ldap_get_values(a->ldap, entry, AMD_LDAP_TSATTR);
425   if (ldap_count_values(vals) == 0) {
426     plog(XLOG_USER, "Missing timestamp value for map %s\n", map);
427     *ts = 0;
428     ldap_value_free(vals);
429     ldap_msgfree(res);
430     return (ENOENT);
431   }
432   dlog("TS value is:%s:\n", vals[0]);
433 
434   if (vals[0]) {
435     *ts = (time_t) strtol(vals[0], &end, 10);
436     if (end == vals[0]) {
437       plog(XLOG_USER, "Unable to decode ldap timestamp %s for map %s\n",
438 	   vals[0], map);
439       err = ENOENT;
440     }
441     if (!*ts > 0) {
442       plog(XLOG_USER, "Nonpositive timestamp %ld for map %s\n",
443 	   (u_long) *ts, map);
444       err = ENOENT;
445     }
446   } else {
447     plog(XLOG_USER, "Empty timestamp value for map %s\n", map);
448     *ts = 0;
449     err = ENOENT;
450   }
451 
452   ldap_value_free(vals);
453   ldap_msgfree(res);
454   dlog("The timestamp for %s is %ld (err=%d)\n", map, (u_long) *ts, err);
455   return (err);
456 }
457 
458 
459 int
460 amu_ldap_search(mnt_map *m, char *map, char *key, char **pval, time_t *ts)
461 {
462   char **vals, filter[MAXPATHLEN], filter2[2 * MAXPATHLEN];
463   char *f1, *f2;
464   struct timeval tv;
465   int i, err = 0, nvals = 0, nentries = 0;
466   LDAPMessage *entry, *res = NULL;
467   ALD *a = (ALD *) (m->map_data);
468 
469   dlog("-> amu_ldap_search: map <%s>, key <%s>\n", map, key);
470 
471   tv.tv_sec = 2;
472   tv.tv_usec = 0;
473   if (a == NULL) {
474     plog(XLOG_USER, "LDAP panic: no map data\n");
475     return (EIO);
476   }
477   if (amu_ldap_rebind(a))	/* Check that's the handle is still valid */
478     return (ENOENT);
479 
480   xsnprintf(filter, sizeof(filter), AMD_LDAP_FILTER, map, key);
481   /* "*" is special to ldap_search(); run through the filter escaping it. */
482   f1 = filter; f2 = filter2;
483   while (*f1) {
484     if (*f1 == '*') {
485       *f2++ = '\\'; *f2++ = '2'; *f2++ = 'a';
486       f1++;
487     } else {
488       *f2++ = *f1++;
489     }
490   }
491   *f2 = '\0';
492   dlog("Search with filter: <%s>\n", filter2);
493   for (i = 0; i < AMD_LDAP_RETRIES; i++) {
494     err = ldap_search_st(a->ldap,
495 			 gopt.ldap_base,
496 			 LDAP_SCOPE_SUBTREE,
497 			 filter2,
498 			 0,
499 			 0,
500 			 &tv,
501 			 &res);
502     if (err == LDAP_SUCCESS)
503       break;
504     if (res) {
505       ldap_msgfree(res);
506       res = NULL;
507     }
508     plog(XLOG_USER, "LDAP search attempt %d failed: %s\n",
509         i + 1, ldap_err2string(err));
510     if (err != LDAP_TIMEOUT) {
511       dlog("amu_ldap_search: unbinding...\n");
512       amu_ldap_unbind(a->ldap);
513       a->ldap = NULL;
514       if (amu_ldap_rebind(a))
515         return (ENOENT);
516     }
517   }
518 
519   switch (err) {
520   case LDAP_SUCCESS:
521     break;
522   case LDAP_NO_SUCH_OBJECT:
523     dlog("No object\n");
524     if (res)
525       ldap_msgfree(res);
526     return (ENOENT);
527   default:
528     plog(XLOG_USER, "LDAP search failed: %s\n",
529 	 ldap_err2string(err));
530     if (res)
531       ldap_msgfree(res);
532     return (EIO);
533   }
534 
535   nentries = ldap_count_entries(a->ldap, res);
536   dlog("Search found %d entries\n", nentries);
537   if (nentries == 0) {
538     ldap_msgfree(res);
539     return (ENOENT);
540   }
541   entry = ldap_first_entry(a->ldap, res);
542   vals = ldap_get_values(a->ldap, entry, AMD_LDAP_ATTR);
543   nvals = ldap_count_values(vals);
544   if (nvals == 0) {
545     plog(XLOG_USER, "Missing value for %s in map %s\n", key, map);
546     ldap_value_free(vals);
547     ldap_msgfree(res);
548     return (EIO);
549   }
550   dlog("Map %s, %s => %s\n", map, key, vals[0]);
551   if (vals[0]) {
552     if (m->cfm && (m->cfm->cfm_flags & CFM_SUN_MAP_SYNTAX))
553       *pval = sun_entry2amd(key, vals[0]);
554     else
555       *pval = strdup(vals[0]);
556     err = 0;
557   } else {
558     plog(XLOG_USER, "Empty value for %s in map %s\n", key, map);
559     err = ENOENT;
560   }
561   ldap_msgfree(res);
562   ldap_value_free(vals);
563 
564   return (err);
565 }
566 
567 
568 int
569 amu_ldap_mtime(mnt_map *m, char *map, time_t *ts)
570 {
571   ALD *aldh = (ALD *) (m->map_data);
572 
573   if (aldh == NULL) {
574     dlog("LDAP panic: unable to find map data\n");
575     return (ENOENT);
576   }
577   if (amu_ldap_rebind(aldh)) {
578     return (ENOENT);
579   }
580   if (get_ldap_timestamp(aldh, map, ts)) {
581     return (ENOENT);
582   }
583   return (0);
584 }
585