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