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