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