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 */
98typedef struct ald_ent ALD;
99typedef struct cr_ent CR;
100typedef struct he_ent HE_ENT;
101
102/*
103 * STRUCTURES:
104 */
105struct ald_ent {
106  LDAP *ldap;
107  HE_ENT *hostent;
108  CR *credentials;
109  time_t timestamp;
110};
111
112struct cr_ent {
113  char *who;
114  char *pw;
115  int method;
116};
117
118struct he_ent {
119  char *host;
120  int port;
121  struct he_ent *next;
122};
123
124/*
125 * FORWARD DECLARATIONS:
126 */
127static int amu_ldap_rebind(ALD *a);
128static int get_ldap_timestamp(ALD *a, char *map, time_t *ts);
129
130int amu_ldap_init(mnt_map *m, char *map, time_t *tsu);
131int amu_ldap_search(mnt_map *m, char *map, char *key, char **pval, time_t *ts);
132int amu_ldap_mtime(mnt_map *m, char *map, time_t *ts);
133
134/*
135 * FUNCTIONS:
136 */
137
138static void
139he_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
148static HE_ENT *
149string2he(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
182static void
183cr_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 */
196static int
197amu_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
232static void
233ald_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
243int
244amu_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
295static int
296amu_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
362static int
363get_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
459int
460amu_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
568int
569amu_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