Annotation of src/usr.bin/sudo/ldap.c, Revision 1.8
1.1 millert 1: /*
1.6 millert 2: * Copyright (c) 2003-2008 Todd C. Miller <Todd.Miller@courtesan.com>
1.1 millert 3: *
4: * This code is derived from software contributed by Aaron Spangler.
5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18:
19: #include <config.h>
20:
21: #include <sys/types.h>
22: #include <sys/time.h>
23: #include <sys/param.h>
24: #include <sys/stat.h>
25: #include <stdio.h>
26: #ifdef STDC_HEADERS
27: # include <stdlib.h>
28: # include <stddef.h>
29: #else
30: # ifdef HAVE_STDLIB_H
31: # include <stdlib.h>
32: # endif
33: #endif /* STDC_HEADERS */
34: #ifdef HAVE_STRING_H
35: # include <string.h>
36: #else
37: # ifdef HAVE_STRINGS_H
38: # include <strings.h>
39: # endif
40: #endif /* HAVE_STRING_H */
41: #if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS)
42: # include <malloc.h>
43: #endif /* HAVE_MALLOC_H && !STDC_HEADERS */
44: #ifdef HAVE_UNISTD_H
45: # include <unistd.h>
46: #endif /* HAVE_UNISTD_H */
47: #include <ctype.h>
1.6 millert 48: #include <limits.h>
1.1 millert 49: #include <pwd.h>
50: #include <grp.h>
51: #include <netinet/in.h>
52: #include <arpa/inet.h>
53: #include <netdb.h>
54: #ifdef HAVE_ERR_H
55: # include <err.h>
56: #else
57: # include "emul/err.h"
58: #endif /* HAVE_ERR_H */
59: #include <errno.h>
60: #ifdef HAVE_LBER_H
61: # include <lber.h>
62: #endif
63: #include <ldap.h>
1.7 millert 64: #if defined(HAVE_LDAP_SSL_H)
65: # include <ldap_ssl.h>
66: #elif defined(HAVE_MPS_LDAP_SSL_H)
67: # include <mps/ldap_ssl.h>
68: #endif
1.1 millert 69:
70: #include "sudo.h"
71: #include "parse.h"
72:
73: #ifndef lint
1.8 ! millert 74: __unused static const char rcsid[] = "$Sudo: ldap.c,v 1.11.2.38 2008/04/11 14:03:51 millert Exp $";
1.1 millert 75: #endif /* lint */
76:
77: #ifndef LINE_MAX
78: # define LINE_MAX 2048
79: #endif
80:
81: #ifndef LDAP_OPT_SUCCESS
82: # define LDAP_OPT_SUCCESS LDAP_SUCCESS
83: #endif
84:
1.8 ! millert 85: #ifndef LDAPS_PORT
! 86: # define LDAPS_PORT 636
! 87: #endif
! 88:
1.5 millert 89: #define DPRINTF(args, level) if (ldap_conf.debug >= level) warnx args
1.1 millert 90:
1.5 millert 91: #define CONF_BOOL 0
92: #define CONF_INT 1
93: #define CONF_STR 2
94:
95: #define SUDO_LDAP_SSL 1
96: #define SUDO_LDAP_STARTTLS 2
97:
98: struct ldap_config_table {
99: const char *conf_str; /* config file string */
100: short type; /* CONF_BOOL, CONF_INT, CONF_STR */
101: short connected; /* connection-specific value? */
102: int opt_val; /* LDAP_OPT_* (or -1 for sudo internal) */
103: void *valp; /* pointer into ldap_conf */
104: };
1.1 millert 105:
106: /* ldap configuration structure */
107: struct ldap_config {
108: int port;
109: int version;
110: int debug;
1.5 millert 111: int ldap_debug;
1.1 millert 112: int tls_checkpeer;
113: int timelimit;
114: int bind_timelimit;
1.5 millert 115: int ssl_mode;
1.1 millert 116: char *host;
117: char *uri;
118: char *binddn;
119: char *bindpw;
120: char *rootbinddn;
121: char *base;
122: char *ssl;
123: char *tls_cacertfile;
124: char *tls_cacertdir;
125: char *tls_random_file;
126: char *tls_cipher_suite;
127: char *tls_certfile;
128: char *tls_keyfile;
129: } ldap_conf;
130:
1.5 millert 131: struct ldap_config_table ldap_conf_table[] = {
132: { "sudoers_debug", CONF_INT, FALSE, -1, &ldap_conf.debug },
133: { "host", CONF_STR, FALSE, -1, &ldap_conf.host },
134: { "port", CONF_INT, FALSE, -1, &ldap_conf.port },
135: { "ssl", CONF_STR, FALSE, -1, &ldap_conf.ssl },
136: { "sslpath", CONF_STR, FALSE, -1, &ldap_conf.tls_certfile },
137: { "uri", CONF_STR, FALSE, -1, &ldap_conf.uri },
138: #ifdef LDAP_OPT_DEBUG_LEVEL
139: { "debug", CONF_INT, FALSE, LDAP_OPT_DEBUG_LEVEL, &ldap_conf.ldap_debug },
140: #endif
141: #ifdef LDAP_OPT_PROTOCOL_VERSION
142: { "ldap_version", CONF_INT, TRUE, LDAP_OPT_PROTOCOL_VERSION,
143: &ldap_conf.version },
144: #endif
145: #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
146: { "tls_checkpeer", CONF_BOOL, FALSE, LDAP_OPT_X_TLS_REQUIRE_CERT,
147: &ldap_conf.tls_checkpeer },
1.7 millert 148: #else
149: { "tls_checkpeer", CONF_BOOL, FALSE, -1, &ldap_conf.tls_checkpeer },
1.5 millert 150: #endif
151: #ifdef LDAP_OPT_X_TLS_CACERTFILE
152: { "tls_cacertfile", CONF_STR, FALSE, LDAP_OPT_X_TLS_CACERTFILE,
153: &ldap_conf.tls_cacertfile },
154: #endif
155: #ifdef LDAP_OPT_X_TLS_CACERTDIR
156: { "tls_cacertdir", CONF_STR, FALSE, LDAP_OPT_X_TLS_CACERTDIR,
157: &ldap_conf.tls_cacertdir },
158: #endif
159: #ifdef LDAP_OPT_X_TLS_RANDOM_FILE
160: { "tls_randfile", CONF_STR, FALSE, LDAP_OPT_X_TLS_RANDOM_FILE,
161: &ldap_conf.tls_random_file },
162: #endif
163: #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE
164: { "tls_ciphers", CONF_STR, FALSE, LDAP_OPT_X_TLS_CIPHER_SUITE,
165: &ldap_conf.tls_cipher_suite },
166: #endif
167: #ifdef LDAP_OPT_X_TLS_CERTFILE
168: { "tls_cert", CONF_STR, FALSE, LDAP_OPT_X_TLS_CERTFILE,
169: &ldap_conf.tls_certfile },
170: #else
171: { "tls_cert", CONF_STR, FALSE, -1, &ldap_conf.tls_certfile },
172: #endif
173: #ifdef LDAP_OPT_X_TLS_KEYFILE
174: { "tls_key", CONF_STR, FALSE, LDAP_OPT_X_TLS_KEYFILE,
175: &ldap_conf.tls_keyfile },
176: #else
177: { "tls_key", CONF_STR, FALSE, -1, &ldap_conf.tls_keyfile },
178: #endif
179: #ifdef LDAP_OPT_NETWORK_TIMEOUT
180: { "bind_timelimit", CONF_INT, TRUE, -1 /* needs timeval, set manually */,
181: &ldap_conf.bind_timelimit },
182: #elif defined(LDAP_X_OPT_CONNECT_TIMEOUT)
183: { "bind_timelimit", CONF_INT, TRUE, LDAP_X_OPT_CONNECT_TIMEOUT,
184: &ldap_conf.bind_timelimit },
185: #endif
186: { "timelimit", CONF_INT, TRUE, LDAP_OPT_TIMELIMIT, &ldap_conf.timelimit },
187: { "binddn", CONF_STR, FALSE, -1, &ldap_conf.binddn },
188: { "bindpw", CONF_STR, FALSE, -1, &ldap_conf.bindpw },
189: { "rootbinddn", CONF_STR, FALSE, -1, &ldap_conf.rootbinddn },
190: { "sudoers_base", CONF_STR, FALSE, -1, &ldap_conf.base },
191: { NULL }
192: };
193:
1.1 millert 194: static void sudo_ldap_update_defaults __P((LDAP *));
195: static void sudo_ldap_close __P((LDAP *));
196: static LDAP *sudo_ldap_open __P((void));
197:
1.6 millert 198: #ifndef HAVE_LDAP_INITIALIZE
199: /*
200: * For each uri, convert to host:port pairs. For ldaps:// enable SSL
201: * Accepts: uris of the form ldap:/// or ldap://hostname:portnum/
202: * where the trailing slash is optional.
203: */
204: static int
205: sudo_ldap_parse_uri(uri_list)
206: const char *uri_list;
207: {
208: char *buf, *uri, *host, *cp, *port;
209: char hostbuf[LINE_MAX];
210: int nldap = 0, nldaps = 0;
211: int rc = -1;
212:
213: buf = estrdup(uri_list);
214: hostbuf[0] = '\0';
215: for ((uri = strtok(buf, " \t")); uri != NULL; (uri = strtok(NULL, " \t"))) {
216: if (strncasecmp(uri, "ldap://", 7) == 0) {
217: nldap++;
218: host = uri + 7;
219: } else if (strncasecmp(uri, "ldaps://", 8) == 0) {
220: nldaps++;
221: host = uri + 8;
222: } else {
223: warnx("unsupported LDAP uri type: %s", uri);
224: goto done;
225: }
226:
227: /* trim optional trailing slash */
228: if ((cp = strrchr(host, '/')) != NULL && cp[1] == '\0') {
229: *cp = '\0';
230: }
231:
232: if (hostbuf[0] != '\0') {
233: if (strlcat(hostbuf, " ", sizeof(hostbuf)) >= sizeof(hostbuf))
234: goto toobig;
235: }
236:
237: if (*host == '\0')
238: host = "localhost"; /* no host specified, use localhost */
239:
240: if (strlcat(hostbuf, host, sizeof(hostbuf)) >= sizeof(hostbuf))
241: goto toobig;
242:
243: /* If using SSL and no port specified, add port 636 */
244: if (nldaps) {
245: if ((port = strrchr(host, ':')) == NULL || !isdigit(port[1]))
246: if (strlcat(hostbuf, ":636", sizeof(hostbuf)) >= sizeof(hostbuf))
247: goto toobig;
248: }
249: }
250: if (hostbuf[0] == '\0') {
251: warnx("invalid uri: %s", uri_list);
252: goto done;
253: }
254:
255: if (nldaps != 0) {
256: if (nldap != 0) {
257: warnx("cannot mix ldap and ldaps URIs");
258: goto done;
259: }
260: if (ldap_conf.ssl_mode == SUDO_LDAP_STARTTLS) {
261: warnx("cannot mix ldaps and starttls");
262: goto done;
263: }
264: ldap_conf.ssl_mode = SUDO_LDAP_SSL;
265: }
266:
267: free(ldap_conf.host);
268: ldap_conf.host = estrdup(hostbuf);
269: rc = 0;
270:
271: done:
272: efree(buf);
273: return(rc);
274:
275: toobig:
276: errx(1, "sudo_ldap_parse_uri: out of space building hostbuf");
277: }
278: #endif /* HAVE_LDAP_INITIALIZE */
279:
280: static int
281: sudo_ldap_init(ldp, host, port)
282: LDAP **ldp;
283: const char *host;
284: int port;
285: {
286: LDAP *ld = NULL;
287: int rc = LDAP_CONNECT_ERROR;
288:
289: #ifdef HAVE_LDAPSSL_INIT
290: if (ldap_conf.ssl_mode == SUDO_LDAP_SSL) {
291: DPRINTF(("ldapssl_clientauth_init(%s, %s)",
292: ldap_conf.tls_certfile ? ldap_conf.tls_certfile : "NULL",
293: ldap_conf.tls_keyfile ? ldap_conf.tls_keyfile : "NULL"), 2);
294: rc = ldapssl_clientauth_init(ldap_conf.tls_certfile, NULL,
295: ldap_conf.tls_keyfile != NULL, ldap_conf.tls_keyfile, NULL);
296: if (rc != LDAP_SUCCESS) {
297: warnx("unable to initialize SSL cert and key db: %s",
298: ldapssl_err2string(rc));
299: goto done;
300: }
301:
302: DPRINTF(("ldapssl_init(%s, %d, 1)", host, port), 2);
303: if ((ld = ldapssl_init(host, port, 1)) == NULL)
304: goto done;
305: } else
306: #endif
307: {
308: DPRINTF(("ldap_init(%s, %d)", host, port), 2);
309: if ((ld = ldap_init(host, port)) == NULL)
310: goto done;
311: }
312: rc = LDAP_SUCCESS;
313:
314: done:
315: *ldp = ld;
316: return(rc);
317: }
318:
1.1 millert 319: /*
320: * Walk through search results and return TRUE if we have a matching
321: * netgroup, else FALSE.
322: */
323: int
324: sudo_ldap_check_user_netgroup(ld, entry)
325: LDAP *ld;
326: LDAPMessage *entry;
327: {
328: char **v = NULL, **p = NULL;
329: int ret = FALSE;
330:
331: if (!entry)
332: return(ret);
333:
334: /* get the values from the entry */
335: v = ldap_get_values(ld, entry, "sudoUser");
336:
337: /* walk through values */
338: for (p = v; p && *p && !ret; p++) {
339: /* match any */
340: if (netgr_matches(*p, NULL, NULL, user_name))
341: ret = TRUE;
342: DPRINTF(("ldap sudoUser netgroup '%s' ... %s", *p,
343: ret ? "MATCH!" : "not"), 2);
344: }
345:
346: if (v)
347: ldap_value_free(v); /* cleanup */
348:
349: return(ret);
350: }
351:
352: /*
353: * Walk through search results and return TRUE if we have a
354: * host match, else FALSE.
355: */
356: int
357: sudo_ldap_check_host(ld, entry)
358: LDAP *ld;
359: LDAPMessage *entry;
360: {
361: char **v = NULL, **p = NULL;
362: int ret = FALSE;
363:
364: if (!entry)
365: return(ret);
366:
367: /* get the values from the entry */
368: v = ldap_get_values(ld, entry, "sudoHost");
369:
370: /* walk through values */
371: for (p = v; p && *p && !ret; p++) {
372: /* match any or address or netgroup or hostname */
1.4 millert 373: if (!strcmp(*p, "ALL") || addr_matches(*p) ||
1.1 millert 374: netgr_matches(*p, user_host, user_shost, NULL) ||
375: !hostname_matches(user_shost, user_host, *p))
376: ret = TRUE;
377: DPRINTF(("ldap sudoHost '%s' ... %s", *p,
378: ret ? "MATCH!" : "not"), 2);
379: }
380:
381: if (v)
382: ldap_value_free(v); /* cleanup */
383:
384: return(ret);
385: }
386:
387: /*
388: * Walk through search results and return TRUE if we have a runas match,
389: * else FALSE.
390: * Since the runas directive in /etc/sudoers is optional, so is sudoRunAs.
391: */
392: int
393: sudo_ldap_check_runas(ld, entry)
394: LDAP *ld;
395: LDAPMessage *entry;
396: {
397: char **v = NULL, **p = NULL;
398: int ret = FALSE;
399:
400: if (!entry)
401: return(ret);
402:
403: /* get the values from the entry */
404: v = ldap_get_values(ld, entry, "sudoRunAs");
405:
406: /*
407: * BUG:
408: *
409: * if runas is not specified on the command line, the only information
410: * as to which user to run as is in the runas_default option. We should
411: * check to see if we have the local option present. Unfortunately we
412: * don't parse these options until after this routine says yes or no.
413: * The query has already returned, so we could peek at the attribute
414: * values here though.
415: *
416: * For now just require users to always use -u option unless its set
417: * in the global defaults. This behaviour is no different than the global
418: * /etc/sudoers.
419: *
420: * Sigh - maybe add this feature later
421: *
422: */
423:
424: /*
425: * If there are no runas entries, match runas_default against
426: * what the user specified on the command line.
427: */
428: if (!v)
1.2 millert 429: ret = !strcasecmp(runas_pw->pw_name, def_runas_default);
1.1 millert 430:
431: /* walk through values returned, looking for a match */
432: for (p = v; p && *p && !ret; p++) {
1.2 millert 433: switch (*p[0]) {
434: case '+':
435: if (netgr_matches(*p, NULL, NULL, runas_pw->pw_name))
436: ret = TRUE;
437: break;
438: case '%':
439: if (usergr_matches(*p, runas_pw->pw_name, runas_pw))
440: ret = TRUE;
441: break;
442: case 'A':
443: if (strcmp(*p, "ALL") == 0) {
444: ret = TRUE;
445: break;
446: }
447: /* FALLTHROUGH */
448: default:
449: if (strcasecmp(*p, runas_pw->pw_name) == 0)
450: ret = TRUE;
451: break;
452: }
1.1 millert 453: DPRINTF(("ldap sudoRunAs '%s' ... %s", *p,
454: ret ? "MATCH!" : "not"), 2);
455: }
456:
457: if (v)
458: ldap_value_free(v); /* cleanup */
459:
460: return(ret);
461: }
462:
463: /*
464: * Walk through search results and return TRUE if we have a command match.
465: */
466: int
1.4 millert 467: sudo_ldap_check_command(ld, entry, setenv_implied)
1.1 millert 468: LDAP *ld;
469: LDAPMessage *entry;
1.4 millert 470: int *setenv_implied;
1.1 millert 471: {
472: char *allowed_cmnd, *allowed_args, **v = NULL, **p = NULL;
473: int foundbang, ret = FALSE;
474:
475: if (!entry)
476: return(ret);
477:
478: v = ldap_get_values(ld, entry, "sudoCommand");
479:
480: /* get_first_entry */
481: for (p = v; p && *p && ret >= 0; p++) {
482: /* Match against ALL ? */
1.4 millert 483: if (!strcmp(*p, "ALL")) {
1.1 millert 484: ret = TRUE;
1.4 millert 485: if (setenv_implied != NULL)
486: *setenv_implied = TRUE;
1.1 millert 487: DPRINTF(("ldap sudoCommand '%s' ... MATCH!", *p), 2);
488: continue;
489: }
490:
491: /* check for !command */
492: if (**p == '!') {
493: foundbang = TRUE;
494: allowed_cmnd = estrdup(1 + *p); /* !command */
495: } else {
496: foundbang = FALSE;
497: allowed_cmnd = estrdup(*p); /* command */
498: }
499:
500: /* split optional args away from command */
501: allowed_args = strchr(allowed_cmnd, ' ');
502: if (allowed_args)
503: *allowed_args++ = '\0';
504:
505: /* check the command like normal */
506: if (command_matches(allowed_cmnd, allowed_args)) {
507: /*
508: * If allowed (no bang) set ret but keep on checking.
509: * If disallowed (bang), exit loop.
510: */
511: ret = foundbang ? -1 : TRUE;
512: }
513: DPRINTF(("ldap sudoCommand '%s' ... %s", *p,
514: ret == TRUE ? "MATCH!" : "not"), 2);
515:
516: efree(allowed_cmnd); /* cleanup */
517: }
518:
519: if (v)
520: ldap_value_free(v); /* more cleanup */
521:
522: /* return TRUE if we found at least one ALLOW and no DENY */
523: return(ret > 0);
524: }
525:
526: /*
527: * Read sudoOption and modify the defaults as we go. This is used once
528: * from the cn=defaults entry and also once when a final sudoRole is matched.
529: */
530: void
531: sudo_ldap_parse_options(ld, entry)
532: LDAP *ld;
533: LDAPMessage *entry;
534: {
535: char op, *var, *val, **v = NULL, **p = NULL;
536:
537: if (!entry)
538: return;
539:
540: v = ldap_get_values(ld, entry, "sudoOption");
541:
542: /* walk through options */
543: for (p = v; p && *p; p++) {
544:
545: DPRINTF(("ldap sudoOption: '%s'", *p), 2);
546: var = estrdup(*p);
547:
548: /* check for equals sign past first char */
549: val = strchr(var, '=');
550: if (val > var) {
551: *val++ = '\0'; /* split on = and truncate var */
552: op = *(val - 2); /* peek for += or -= cases */
553: if (op == '+' || op == '-') {
554: *(val - 2) = '\0'; /* found, remove extra char */
555: /* case var+=val or var-=val */
556: set_default(var, val, (int) op);
557: } else {
558: /* case var=val */
559: set_default(var, val, TRUE);
560: }
561: } else if (*var == '!') {
562: /* case !var Boolean False */
563: set_default(var + 1, NULL, FALSE);
564: } else {
565: /* case var Boolean True */
566: set_default(var, NULL, TRUE);
567: }
568: efree(var);
569: }
570:
571: if (v)
572: ldap_value_free(v);
573: }
574:
575: /*
576: * Concatenate strings, dynamically growing them as necessary.
577: * Strings can be arbitrarily long and are allocated/reallocated on
578: * the fly. Make sure to free them when you are done.
579: *
580: * Usage:
581: *
582: * char *s=NULL;
583: * size_t sz;
584: *
585: * ncat(&s,&sz,"This ");
586: * ncat(&s,&sz,"is ");
587: * ncat(&s,&sz,"an ");
588: * ncat(&s,&sz,"arbitrarily ");
589: * ncat(&s,&sz,"long ");
590: * ncat(&s,&sz,"string!");
591: *
592: * printf("String Value='%s', but has %d bytes allocated\n",s,sz);
593: *
594: */
595: void
596: ncat(s, sz, src)
597: char **s;
598: size_t *sz;
599: char *src;
600: {
601: size_t nsz;
602:
603: /* handle initial alloc */
604: if (*s == NULL) {
605: *s = estrdup(src);
606: *sz = strlen(src) + 1;
607: return;
608: }
609: /* handle realloc */
610: nsz = strlen(*s) + strlen(src) + 1;
611: if (*sz < nsz)
612: *s = erealloc((void *) *s, *sz = nsz * 2);
613: strlcat(*s, src, *sz);
614: }
615:
616: /*
617: * builds together a filter to check against ldap
618: */
619: char *
620: sudo_ldap_build_pass1()
621: {
622: struct group *grp;
623: size_t sz;
624: char *b = NULL;
625: int i;
626:
627: /* global OR */
628: ncat(&b, &sz, "(|");
629:
630: /* build filter sudoUser=user_name */
631: ncat(&b, &sz, "(sudoUser=");
632: ncat(&b, &sz, user_name);
633: ncat(&b, &sz, ")");
634:
635: /* Append primary group */
636: grp = getgrgid(user_gid);
637: if (grp != NULL) {
638: ncat(&b, &sz, "(sudoUser=%");
639: ncat(&b, &sz, grp -> gr_name);
640: ncat(&b, &sz, ")");
641: }
642:
643: /* Append supplementary groups */
644: for (i = 0; i < user_ngroups; i++) {
1.6 millert 645: if (user_groups[i] == user_gid)
646: continue;
1.1 millert 647: if ((grp = getgrgid(user_groups[i])) != NULL) {
648: ncat(&b, &sz, "(sudoUser=%");
649: ncat(&b, &sz, grp -> gr_name);
650: ncat(&b, &sz, ")");
651: }
652: }
653:
654: /* Add ALL to list */
655: ncat(&b, &sz, "(sudoUser=ALL)");
656:
657: /* End of OR List */
658: ncat(&b, &sz, ")");
659:
660: return(b);
661: }
662:
663: /*
664: * Map yes/true/on to TRUE, no/false/off to FALSE, else -1
665: */
666: int
667: _atobool(s)
668: const char *s;
669: {
670: switch (*s) {
671: case 'y':
672: case 'Y':
673: if (strcasecmp(s, "yes") == 0)
674: return(TRUE);
675: break;
676: case 't':
677: case 'T':
678: if (strcasecmp(s, "true") == 0)
679: return(TRUE);
680: break;
681: case 'o':
682: case 'O':
683: if (strcasecmp(s, "on") == 0)
684: return(TRUE);
685: if (strcasecmp(s, "off") == 0)
686: return(FALSE);
687: break;
688: case 'n':
689: case 'N':
690: if (strcasecmp(s, "no") == 0)
691: return(FALSE);
692: break;
693: case 'f':
694: case 'F':
695: if (strcasecmp(s, "false") == 0)
696: return(FALSE);
697: break;
698: }
699: return(-1);
700: }
701:
702: int
703: sudo_ldap_read_config()
704: {
705: FILE *f;
706: char buf[LINE_MAX], *c, *keyword, *value;
1.5 millert 707: struct ldap_config_table *cur;
1.1 millert 708:
709: /* defaults */
710: ldap_conf.version = 3;
1.5 millert 711: ldap_conf.port = -1;
1.1 millert 712: ldap_conf.tls_checkpeer = -1;
713: ldap_conf.timelimit = -1;
714: ldap_conf.bind_timelimit = -1;
715:
716: if ((f = fopen(_PATH_LDAP_CONF, "r")) == NULL)
717: return(FALSE);
1.5 millert 718:
1.1 millert 719: while (fgets(buf, sizeof(buf), f)) {
720: /* ignore text after comment character */
721: if ((c = strchr(buf, '#')) != NULL)
722: *c = '\0';
723:
724: /* skip leading whitespace */
725: for (c = buf; isspace((unsigned char) *c); c++)
726: /* nothing */;
727:
728: if (*c == '\0' || *c == '\n')
729: continue; /* skip empty line */
730:
731: /* properly terminate keyword string */
732: keyword = c;
733: while (*c && !isspace((unsigned char) *c))
734: c++;
735: if (*c)
736: *c++ = '\0'; /* terminate keyword */
737:
738: /* skip whitespace before value */
739: while (isspace((unsigned char) *c))
740: c++;
741: value = c;
742:
743: /* trim whitespace after value */
744: while (*c)
745: c++; /* wind to end */
746: while (--c > value && isspace((unsigned char) *c))
747: *c = '\0';
748:
1.5 millert 749: /* Look up keyword in config table. */
750: for (cur = ldap_conf_table; cur->conf_str != NULL; cur++) {
751: if (strcasecmp(keyword, cur->conf_str) == 0) {
752: switch (cur->type) {
753: case CONF_BOOL:
754: *(int *)(cur->valp) = _atobool(value);
755: break;
756: case CONF_INT:
757: *(int *)(cur->valp) = atoi(value);
758: break;
759: case CONF_STR:
760: efree(*(char **)(cur->valp));
761: *(char **)(cur->valp) = estrdup(value);
762: break;
763: }
764: break;
765: }
1.1 millert 766: }
767: }
768: fclose(f);
769:
770: if (!ldap_conf.host)
1.5 millert 771: ldap_conf.host = "localhost";
1.1 millert 772:
773: if (ldap_conf.bind_timelimit > 0)
774: ldap_conf.bind_timelimit *= 1000; /* convert to ms */
775:
776: if (ldap_conf.debug > 1) {
777: fprintf(stderr, "LDAP Config Summary\n");
778: fprintf(stderr, "===================\n");
779: if (ldap_conf.uri) {
780: fprintf(stderr, "uri %s\n", ldap_conf.uri);
1.6 millert 781: } else {
1.1 millert 782: fprintf(stderr, "host %s\n", ldap_conf.host ?
783: ldap_conf.host : "(NONE)");
784: fprintf(stderr, "port %d\n", ldap_conf.port);
785: }
786: fprintf(stderr, "ldap_version %d\n", ldap_conf.version);
787:
788: fprintf(stderr, "sudoers_base %s\n", ldap_conf.base ?
789: ldap_conf.base : "(NONE) <---Sudo will ignore ldap)");
790: fprintf(stderr, "binddn %s\n", ldap_conf.binddn ?
791: ldap_conf.binddn : "(anonymous)");
792: fprintf(stderr, "bindpw %s\n", ldap_conf.bindpw ?
793: ldap_conf.bindpw : "(anonymous)");
1.5 millert 794: if (ldap_conf.bind_timelimit > 0)
795: fprintf(stderr, "bind_timelimit %d\n", ldap_conf.bind_timelimit);
796: if (ldap_conf.timelimit > 0)
797: fprintf(stderr, "timelimit %d\n", ldap_conf.timelimit);
1.1 millert 798: fprintf(stderr, "ssl %s\n", ldap_conf.ssl ?
799: ldap_conf.ssl : "(no)");
1.5 millert 800: if (ldap_conf.tls_checkpeer != -1)
801: fprintf(stderr, "tls_checkpeer %s\n", ldap_conf.tls_checkpeer ?
802: "(yes)" : "(no)");
803: if (ldap_conf.tls_cacertfile != NULL)
804: fprintf(stderr, "tls_cacertfile %s\n", ldap_conf.tls_cacertfile);
805: if (ldap_conf.tls_cacertdir != NULL)
806: fprintf(stderr, "tls_cacertdir %s\n", ldap_conf.tls_cacertdir);
807: if (ldap_conf.tls_random_file != NULL)
808: fprintf(stderr, "tls_random_file %s\n", ldap_conf.tls_random_file);
809: if (ldap_conf.tls_cipher_suite != NULL)
810: fprintf(stderr, "tls_cipher_suite %s\n", ldap_conf.tls_cipher_suite);
811: if (ldap_conf.tls_certfile != NULL)
812: fprintf(stderr, "tls_certfile %s\n", ldap_conf.tls_certfile);
813: if (ldap_conf.tls_keyfile != NULL)
814: fprintf(stderr, "tls_keyfile %s\n", ldap_conf.tls_keyfile);
1.1 millert 815: fprintf(stderr, "===================\n");
816: }
817: if (!ldap_conf.base)
818: return(FALSE); /* if no base is defined, ignore LDAP */
819:
1.5 millert 820: /*
821: * Interpret SSL option
822: */
823: if (ldap_conf.ssl != NULL) {
1.7 millert 824: if (strcasecmp(ldap_conf.ssl, "start_tls") == 0)
825: ldap_conf.ssl_mode = SUDO_LDAP_STARTTLS;
826: else if (_atobool(ldap_conf.ssl))
827: ldap_conf.ssl_mode = SUDO_LDAP_SSL;
1.5 millert 828: }
1.7 millert 829:
830: #if defined(HAVE_LDAPSSL_SET_STRENGTH) && !defined(LDAP_OPT_X_TLS_REQUIRE_CERT)
831: if (ldap_conf.tls_checkpeer != -1) {
832: ldapssl_set_strength(NULL,
833: ldap_conf.tls_checkpeer ? LDAPSSL_AUTH_CERT : LDAPSSL_AUTH_WEAK);
834: }
835: #endif
1.5 millert 836:
1.6 millert 837: #ifndef HAVE_LDAP_INITIALIZE
838: /* Convert uri list to host list if no ldap_initialize(). */
839: if (ldap_conf.uri) {
840: if (sudo_ldap_parse_uri(ldap_conf.uri) != 0)
841: return(FALSE);
842: free(ldap_conf.uri);
843: ldap_conf.uri = NULL;
844: ldap_conf.port = LDAP_PORT;
845: }
846: #endif
847:
1.5 millert 848: /* Use port 389 for plaintext LDAP and port 636 for SSL LDAP */
1.6 millert 849: if (!ldap_conf.uri && ldap_conf.port < 0)
1.5 millert 850: ldap_conf.port =
851: ldap_conf.ssl_mode == SUDO_LDAP_SSL ? LDAPS_PORT : LDAP_PORT;
852:
1.1 millert 853: /* If rootbinddn set, read in /etc/ldap.secret if it exists. */
854: if (ldap_conf.rootbinddn) {
855: if ((f = fopen(_PATH_LDAP_SECRET, "r")) != NULL) {
856: if (fgets(buf, sizeof(buf), f) != NULL) {
857: /* removing trailing newlines */
858: for (c = buf; *c != '\0'; c++)
859: continue;
860: while (--c > buf && *c == '\n')
861: *c = '\0';
862: /* copy to bindpw and binddn */
863: efree(ldap_conf.bindpw);
864: ldap_conf.bindpw = estrdup(buf);
865: efree(ldap_conf.binddn);
866: ldap_conf.binddn = ldap_conf.rootbinddn;
867: ldap_conf.rootbinddn = NULL;
868: }
869: fclose(f);
870: }
871: }
872: return(TRUE);
873: }
874:
875: /*
876: * like perl's join(sep,@ARGS)
877: */
878: char *
879: _ldap_join_values(sep, v)
880: char *sep;
881: char **v;
882: {
883: char *b = NULL, **p = NULL;
884: size_t sz = 0;
885:
886: /* paste values together */
887: for (p = v; p && *p; p++) {
888: if (p != v && sep != NULL)
1.3 martynas 889: ncat(&b, &sz, sep); /* append separator */
1.1 millert 890: ncat(&b, &sz, *p); /* append value */
891: }
892:
893: /* sanity check */
894: if (b[0] == '\0') {
895: /* something went wrong, put something here */
896: ncat(&b, &sz, "(empty list)"); /* append value */
897: }
898:
899: return(b);
900: }
901:
902: char *sudo_ldap_cm_list = NULL;
903: size_t sudo_ldap_cm_list_size;
904:
905: #define SAVE_LIST(x) ncat(&sudo_ldap_cm_list,&sudo_ldap_cm_list_size,(x))
906: /*
907: * Walks through search result and returns TRUE if we have a
908: * command match
909: */
910: int
911: sudo_ldap_add_match(ld, entry, pwflag)
912: LDAP *ld;
913: LDAPMessage *entry;
914: int pwflag;
915: {
916: char *dn, **edn, **v = NULL;
917:
918: /* if we are not collecting matches, then don't save them */
919: if (pwflag != I_LISTPW)
920: return(TRUE);
921:
922: /* collect the dn, only show the rdn */
923: dn = ldap_get_dn(ld, entry);
924: edn = dn ? ldap_explode_dn(dn, 1) : NULL;
925: SAVE_LIST("\nLDAP Role: ");
926: SAVE_LIST((edn && *edn) ? *edn : "UNKNOWN");
927: SAVE_LIST("\n");
928: if (dn)
929: ldap_memfree(dn);
930: if (edn)
931: ldap_value_free(edn);
932:
933: /* get the Runas Values from the entry */
934: v = ldap_get_values(ld, entry, "sudoRunAs");
935: if (v && *v) {
936: SAVE_LIST(" RunAs: (");
937: SAVE_LIST(_ldap_join_values(", ", v));
938: SAVE_LIST(")\n");
939: }
940: if (v)
941: ldap_value_free(v);
942:
943: /* get the Command Values from the entry */
944: v = ldap_get_values(ld, entry, "sudoCommand");
945: if (v && *v) {
946: SAVE_LIST(" Commands:\n ");
947: SAVE_LIST(_ldap_join_values("\n ", v));
948: SAVE_LIST("\n");
949: } else {
950: SAVE_LIST(" Commands: NONE\n");
951: }
952: if (v)
953: ldap_value_free(v);
954:
955: return(FALSE); /* Don't stop at the first match */
956: }
957: #undef SAVE_LIST
958:
959: void
960: sudo_ldap_list_matches()
961: {
962: if (sudo_ldap_cm_list != NULL)
963: printf("%s", sudo_ldap_cm_list);
964: }
965:
966: /*
1.5 millert 967: * Set LDAP options based on the config table.
1.1 millert 968: */
1.5 millert 969: int
970: sudo_ldap_set_options(ld)
971: LDAP *ld;
1.1 millert 972: {
1.5 millert 973: struct ldap_config_table *cur;
1.1 millert 974: int rc;
975:
1.5 millert 976: /* Set ber options */
977: #ifdef LBER_OPT_DEBUG_LEVEL
978: if (ldap_conf.ldap_debug)
979: ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &ldap_conf.ldap_debug);
980: #endif
1.1 millert 981:
1.5 millert 982: /* Set simple LDAP options */
983: for (cur = ldap_conf_table; cur->conf_str != NULL; cur++) {
984: LDAP *conn;
985: int ival;
986: char *sval;
1.1 millert 987:
1.5 millert 988: if (cur->opt_val == -1)
989: continue;
1.1 millert 990:
1.5 millert 991: conn = cur->connected ? ld : NULL;
992: switch (cur->type) {
993: case CONF_BOOL:
994: case CONF_INT:
995: ival = *(int *)(cur->valp);
996: if (ival >= 0) {
997: rc = ldap_set_option(conn, cur->opt_val, &ival);
998: if (rc != LDAP_OPT_SUCCESS) {
999: warnx("ldap_set_option: %s -> %d: %s",
1000: cur->conf_str, ival, ldap_err2string(rc));
1001: return(-1);
1002: }
1003: DPRINTF(("ldap_set_option: %s -> %d", cur->conf_str, ival), 1);
1004: }
1005: break;
1006: case CONF_STR:
1007: sval = *(char **)(cur->valp);
1008: if (sval != NULL) {
1009: rc = ldap_set_option(conn, cur->opt_val, sval);
1010: if (rc != LDAP_OPT_SUCCESS) {
1011: warnx("ldap_set_option: %s -> %s: %s",
1012: cur->conf_str, sval, ldap_err2string(rc));
1013: return(-1);
1014: }
1015: DPRINTF(("ldap_set_option: %s -> %s", cur->conf_str, sval), 1);
1016: }
1017: break;
1018: }
1019: }
1.1 millert 1020:
1021: #ifdef LDAP_OPT_NETWORK_TIMEOUT
1.5 millert 1022: /* Convert bind_timelimit to a timeval */
1.1 millert 1023: if (ldap_conf.bind_timelimit > 0) {
1024: struct timeval tv;
1025: tv.tv_sec = ldap_conf.bind_timelimit / 1000;
1026: tv.tv_usec = 0;
1027: rc = ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &tv);
1028: if (rc != LDAP_OPT_SUCCESS) {
1.5 millert 1029: warnx("ldap_set_option(NETWORK_TIMEOUT, %ld): %s",
1030: (long)tv.tv_sec, ldap_err2string(rc));
1031: return(-1);
1032: }
1033: DPRINTF(("ldap_set_option(LDAP_OPT_NETWORK_TIMEOUT, %ld)\n",
1034: (long)tv.tv_sec), 1);
1035: }
1036: #endif
1037:
1038: #if defined(LDAP_OPT_X_TLS) && !defined(HAVE_LDAPSSL_INIT)
1039: if (ldap_conf.ssl_mode == SUDO_LDAP_SSL) {
1040: int val = LDAP_OPT_X_TLS_HARD;
1041: rc = ldap_set_option(ld, LDAP_OPT_X_TLS, &val);
1042: if (rc != LDAP_SUCCESS) {
1043: warnx("ldap_set_option(LDAP_OPT_X_TLS, LDAP_OPT_X_TLS_HARD): %s",
1044: ldap_err2string(rc));
1045: return(-1);
1046: }
1.6 millert 1047: DPRINTF(("ldap_set_option(LDAP_OPT_X_TLS, LDAP_OPT_X_TLS_HARD)\n"), 1);
1.5 millert 1048: }
1049: #endif
1050: return(0);
1051: }
1052:
1053: /*
1054: * Open a connection to the LDAP server.
1055: */
1056: static LDAP *
1057: sudo_ldap_open()
1058: {
1059: LDAP *ld = NULL;
1060: int rc;
1061:
1062: if (!sudo_ldap_read_config())
1063: return(NULL);
1064:
1065: /* Connect to LDAP server */
1.1 millert 1066: #ifdef HAVE_LDAP_INITIALIZE
1.6 millert 1067: if (ldap_conf.uri != NULL) {
1.5 millert 1068: DPRINTF(("ldap_initialize(ld, %s)", ldap_conf.uri), 2);
1.1 millert 1069: rc = ldap_initialize(&ld, ldap_conf.uri);
1070: } else
1071: #endif /* HAVE_LDAP_INITIALIZE */
1.6 millert 1072: rc = sudo_ldap_init(&ld, ldap_conf.host, ldap_conf.port);
1073: if (rc != LDAP_SUCCESS) {
1074: warnx("unable to initialize LDAP: %s", ldap_err2string(rc));
1075: return(NULL);
1.1 millert 1076: }
1077:
1.5 millert 1078: /* Set LDAP options */
1079: if (sudo_ldap_set_options(ld) < 0)
1080: return(NULL);
1.1 millert 1081:
1.5 millert 1082: if (ldap_conf.ssl_mode == SUDO_LDAP_STARTTLS) {
1.1 millert 1083: #ifdef HAVE_LDAP_START_TLS_S
1084: rc = ldap_start_tls_s(ld, NULL, NULL);
1085: if (rc != LDAP_SUCCESS) {
1.5 millert 1086: warnx("ldap_start_tls_s(): %s", ldap_err2string(rc));
1.1 millert 1087: ldap_unbind(ld);
1088: return(NULL);
1089: }
1090: DPRINTF(("ldap_start_tls_s() ok"), 1);
1.5 millert 1091: #else
1092: warnx("start_tls specified but LDAP libs do not support ldap_start_tls_s()");
1093: #endif /* HAVE_LDAP_START_TLS_S */
1.1 millert 1094: }
1095:
1096: /* Actually connect */
1097: if ((rc = ldap_simple_bind_s(ld, ldap_conf.binddn, ldap_conf.bindpw))) {
1.5 millert 1098: warnx("ldap_simple_bind_s: %s", ldap_err2string(rc));
1.1 millert 1099: return(NULL);
1100: }
1.5 millert 1101: DPRINTF(("ldap_simple_bind_s() ok"), 1);
1.1 millert 1102:
1103: return(ld);
1104: }
1105:
1106: static void
1107: sudo_ldap_update_defaults(ld)
1108: LDAP *ld;
1109: {
1110: LDAPMessage *entry = NULL, *result = NULL; /* used for searches */
1111: int rc; /* temp return value */
1112:
1113: rc = ldap_search_s(ld, ldap_conf.base, LDAP_SCOPE_SUBTREE,
1114: "cn=defaults", NULL, 0, &result);
1.5 millert 1115: if (rc == LDAP_SUCCESS && (entry = ldap_first_entry(ld, result))) {
1.1 millert 1116: DPRINTF(("found:%s", ldap_get_dn(ld, entry)), 1);
1117: sudo_ldap_parse_options(ld, entry);
1118: } else
1119: DPRINTF(("no default options found!"), 1);
1120:
1121: if (result)
1122: ldap_msgfree(result);
1123: }
1124:
1125: /*
1126: * like sudoers_lookup() - only LDAP style
1127: */
1128: int
1129: sudo_ldap_check(pwflag)
1130: int pwflag;
1131: {
1132: LDAP *ld;
1133: LDAPMessage *entry = NULL, *result = NULL; /* used for searches */
1134: char *filt; /* used to parse attributes */
1135: int rc, ret = FALSE, do_netgr; /* temp/final return values */
1.4 millert 1136: int setenv_implied;
1.1 millert 1137: int ldap_user_matches = FALSE, ldap_host_matches = FALSE; /* flags */
1138:
1139: /* Open a connection to the LDAP server. */
1140: if ((ld = sudo_ldap_open()) == NULL)
1141: return(VALIDATE_ERROR);
1142:
1143: /* Parse Default options. */
1144: sudo_ldap_update_defaults(ld);
1145:
1146: /*
1147: * Okay - time to search for anything that matches this user
1148: * Lets limit it to only two queries of the LDAP server
1149: *
1150: * The first pass will look by the username, groups, and
1151: * the keyword ALL. We will then inspect the results that
1152: * came back from the query. We don't need to inspect the
1153: * sudoUser in this pass since the LDAP server already scanned
1154: * it for us.
1155: *
1156: * The second pass will return all the entries that contain
1157: * user netgroups. Then we take the netgroups returned and
1158: * try to match them against the username.
1159: */
1.4 millert 1160: setenv_implied = FALSE;
1.1 millert 1161: for (do_netgr = 0; !ret && do_netgr < 2; do_netgr++) {
1162: filt = do_netgr ? estrdup("sudoUser=+*") : sudo_ldap_build_pass1();
1163: DPRINTF(("ldap search '%s'", filt), 1);
1164: rc = ldap_search_s(ld, ldap_conf.base, LDAP_SCOPE_SUBTREE, filt,
1165: NULL, 0, &result);
1.5 millert 1166: if (rc != LDAP_SUCCESS)
1.1 millert 1167: DPRINTF(("nothing found for '%s'", filt), 1);
1168: efree(filt);
1169:
1170: /* parse each entry returned from this most recent search */
1171: entry = rc ? NULL : ldap_first_entry(ld, result);
1172: while (entry != NULL) {
1173: DPRINTF(("found:%s", ldap_get_dn(ld, entry)), 1);
1174: if (
1175: /* first verify user netgroup matches - only if in pass 2 */
1176: (!do_netgr || sudo_ldap_check_user_netgroup(ld, entry)) &&
1177: /* remember that user matched */
1178: (ldap_user_matches = -1) &&
1179: /* verify host match */
1180: sudo_ldap_check_host(ld, entry) &&
1181: /* remember that host matched */
1182: (ldap_host_matches = -1) &&
1183: /* add matches for listing later */
1184: sudo_ldap_add_match(ld, entry, pwflag) &&
1185: /* verify command match */
1.4 millert 1186: sudo_ldap_check_command(ld, entry, &setenv_implied) &&
1.1 millert 1187: /* verify runas match */
1188: sudo_ldap_check_runas(ld, entry)
1189: ) {
1190: /* We have a match! */
1191: DPRINTF(("Perfect Matched!"), 1);
1192: /* pick up any options */
1.4 millert 1193: if (setenv_implied)
1194: def_setenv = TRUE;
1.1 millert 1195: sudo_ldap_parse_options(ld, entry);
1.8 ! millert 1196: #ifdef HAVE_SELINUX
! 1197: /* Set role and type if not specified on command line. */
! 1198: if (user_role == NULL)
! 1199: user_role = def_role;
! 1200: if (user_type == NULL)
! 1201: user_type = def_type;
! 1202: #endif /* HAVE_SELINUX */
1.1 millert 1203: /* make sure we don't reenter loop */
1204: ret = VALIDATE_OK;
1205: /* break from inside for loop */
1206: break;
1207: }
1208: entry = ldap_next_entry(ld, entry);
1209: }
1210: if (result)
1211: ldap_msgfree(result);
1212: result = NULL;
1213: }
1214:
1215: sudo_ldap_close(ld); /* shut down connection */
1216:
1217: DPRINTF(("user_matches=%d", ldap_user_matches), 1);
1218: DPRINTF(("host_matches=%d", ldap_host_matches), 1);
1219:
1220: /* Check for special case for -v, -k, -l options */
1221: if (pwflag && ldap_user_matches && ldap_host_matches) {
1222: /*
1223: * Handle verifypw & listpw
1224: *
1225: * To be extra paranoid, since we haven't read any NOPASSWD options
1226: * in /etc/sudoers yet, but we have to make the decission now, lets
1227: * assume the worst and prefer to prompt for password unless the setting
1228: * is "never". (example verifypw=never or listpw=never)
1229: *
1230: */
1231: ret = VALIDATE_OK;
1232: if (pwflag == -1) {
1233: SET(ret, FLAG_NOPASS); /* -k or -K */
1234: } else {
1235: switch (sudo_defs_table[pwflag].sd_un.tuple) {
1236: case never:
1237: SET(ret, FLAG_NOPASS);
1238: break;
1239: case always:
1240: if (def_authenticate)
1241: SET(ret, FLAG_CHECK_USER);
1242: break;
1243: default:
1244: break;
1245: }
1246: }
1247: }
1248: if (ISSET(ret, VALIDATE_OK)) {
1249: /* we have a match, should we check the password? */
1250: if (!def_authenticate)
1251: SET(ret, FLAG_NOPASS);
1252: if (def_noexec)
1253: SET(ret, FLAG_NOEXEC);
1254: if (def_setenv)
1255: SET(ret, FLAG_SETENV);
1256: } else {
1257: /* we do not have a match */
1258: ret = VALIDATE_NOT_OK;
1259: if (pwflag)
1260: SET(ret, FLAG_NO_CHECK);
1261: else if (!ldap_user_matches)
1262: SET(ret, FLAG_NO_USER);
1263: else if (!ldap_host_matches)
1264: SET(ret, FLAG_NO_HOST);
1265: }
1266: DPRINTF(("sudo_ldap_check(%d)=0x%02x", pwflag, ret), 1);
1267:
1268: return(ret);
1269: }
1270:
1271: /*
1272: * shut down LDAP connection
1273: */
1274: static void
1275: sudo_ldap_close(LDAP *ld)
1276: {
1277: if (ld)
1278: ldap_unbind_s(ld);
1279: }