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 */
95typedef struct ald_ent ALD;
96typedef struct cr_ent CR;
97typedef struct he_ent HE_ENT;
98
99/*
100 * STRUCTURES:
101 */
102struct ald_ent {
103  LDAP *ldap;
104  HE_ENT *hostent;
105  CR *credentials;
106  time_t timestamp;
107};
108
109struct cr_ent {
110  char *who;
111  char *pw;
112  int method;
113};
114
115struct he_ent {
116  char *host;
117  int port;
118  struct he_ent *next;
119};
120
121/*
122 * FORWARD DECLARATIONS:
123 */
124static int amu_ldap_rebind(ALD *a);
125static int get_ldap_timestamp(ALD *a, char *map, time_t *ts);
126
127
128/*
129 * FUNCTIONS:
130 */
131
132static void
133he_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
142static HE_ENT *
143string2he(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
174static void
175cr_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 */
188static int
189amu_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
224static void
225ald_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
235int
236amu_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
287static int
288amu_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
354static int
355get_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
451int
452amu_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
557int
558amu_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}
574