xref: /netbsd-src/external/bsd/am-utils/dist/amd/info_ldap.c (revision a83f6c13171e562ca1a870d6e82ace3a77e252a1)
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