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 */
92typedef struct ald_ent ALD;
93typedef struct cr_ent CR;
94typedef struct he_ent HE_ENT;
95
96/*
97 * STRUCTURES:
98 */
99struct ald_ent {
100  LDAP *ldap;
101  HE_ENT *hostent;
102  CR *credentials;
103  time_t timestamp;
104};
105
106struct cr_ent {
107  char *who;
108  char *pw;
109  int method;
110};
111
112struct he_ent {
113  char *host;
114  int port;
115  struct he_ent *next;
116};
117
118static ALD *ldap_connection;
119
120/*
121 * FORWARD DECLARATIONS:
122 */
123static int amu_ldap_rebind(ALD *a);
124static int get_ldap_timestamp(ALD *a, char *map, time_t *ts);
125
126int amu_ldap_init(mnt_map *m, char *map, time_t *tsu);
127int amu_ldap_search(mnt_map *m, char *map, char *key, char **pval, time_t *ts);
128int amu_ldap_mtime(mnt_map *m, char *map, time_t *ts);
129
130/*
131 * FUNCTIONS:
132 */
133
134static void
135he_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
144static HE_ENT *
145string2he(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
179static void
180cr_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 */
193static int
194amu_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
229static void
230ald_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
240int
241amu_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
303static int
304amu_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
370static int
371get_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
467int
468amu_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
576int
577amu_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}
593