Annotation of src/usr.bin/sudo/ldap.c, Revision 1.4
1.1 millert 1: /*
2: * Copyright (c) 2003-2005 Todd C. Miller <Todd.Miller@courtesan.com>
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>
48: #include <pwd.h>
49: #include <grp.h>
50: #include <netinet/in.h>
51: #include <arpa/inet.h>
52: #include <netdb.h>
53: #ifdef HAVE_ERR_H
54: # include <err.h>
55: #else
56: # include "emul/err.h"
57: #endif /* HAVE_ERR_H */
58: #include <errno.h>
59: #ifdef HAVE_LBER_H
60: # include <lber.h>
61: #endif
62: #include <ldap.h>
63:
64: #include "sudo.h"
65: #include "parse.h"
66:
67: #ifndef lint
1.4 ! millert 68: __unused static const char rcsid[] = "$Sudo: ldap.c,v 1.11.2.20 2007/11/27 17:06:54 millert Exp $";
1.1 millert 69: #endif /* lint */
70:
71: #ifndef LINE_MAX
72: # define LINE_MAX 2048
73: #endif
74:
75: #ifndef LDAP_OPT_SUCCESS
76: # define LDAP_OPT_SUCCESS LDAP_SUCCESS
77: #endif
78:
79: #if defined(LDAP_X_OPT_CONNECT_TIMEOUT) && !defined(LDAP_OPT_X_CONNECT_TIMEOUT)
80: #define LDAP_OPT_X_CONNECT_TIMEOUT LDAP_OPT_X_CONNECT_TIMEOUT
81: #endif
82:
83: #define DPRINTF(args, level) if (ldap_conf.debug >= level) warnx args
84:
85: /* ldap configuration structure */
86: struct ldap_config {
87: int port;
88: int version;
89: int debug;
90: int tls_checkpeer;
91: int timelimit;
92: int bind_timelimit;
93: char *host;
94: char *uri;
95: char *binddn;
96: char *bindpw;
97: char *rootbinddn;
98: char *base;
99: char *ssl;
100: char *tls_cacertfile;
101: char *tls_cacertdir;
102: char *tls_random_file;
103: char *tls_cipher_suite;
104: char *tls_certfile;
105: char *tls_keyfile;
106: } ldap_conf;
107:
108: static void sudo_ldap_update_defaults __P((LDAP *));
109: static void sudo_ldap_close __P((LDAP *));
110: static LDAP *sudo_ldap_open __P((void));
111:
112: /*
113: * Walk through search results and return TRUE if we have a matching
114: * netgroup, else FALSE.
115: */
116: int
117: sudo_ldap_check_user_netgroup(ld, entry)
118: LDAP *ld;
119: LDAPMessage *entry;
120: {
121: char **v = NULL, **p = NULL;
122: int ret = FALSE;
123:
124: if (!entry)
125: return(ret);
126:
127: /* get the values from the entry */
128: v = ldap_get_values(ld, entry, "sudoUser");
129:
130: /* walk through values */
131: for (p = v; p && *p && !ret; p++) {
132: /* match any */
133: if (netgr_matches(*p, NULL, NULL, user_name))
134: ret = TRUE;
135: DPRINTF(("ldap sudoUser netgroup '%s' ... %s", *p,
136: ret ? "MATCH!" : "not"), 2);
137: }
138:
139: if (v)
140: ldap_value_free(v); /* cleanup */
141:
142: return(ret);
143: }
144:
145: /*
146: * Walk through search results and return TRUE if we have a
147: * host match, else FALSE.
148: */
149: int
150: sudo_ldap_check_host(ld, entry)
151: LDAP *ld;
152: LDAPMessage *entry;
153: {
154: char **v = NULL, **p = NULL;
155: int ret = FALSE;
156:
157: if (!entry)
158: return(ret);
159:
160: /* get the values from the entry */
161: v = ldap_get_values(ld, entry, "sudoHost");
162:
163: /* walk through values */
164: for (p = v; p && *p && !ret; p++) {
165: /* match any or address or netgroup or hostname */
1.4 ! millert 166: if (!strcmp(*p, "ALL") || addr_matches(*p) ||
1.1 millert 167: netgr_matches(*p, user_host, user_shost, NULL) ||
168: !hostname_matches(user_shost, user_host, *p))
169: ret = TRUE;
170: DPRINTF(("ldap sudoHost '%s' ... %s", *p,
171: ret ? "MATCH!" : "not"), 2);
172: }
173:
174: if (v)
175: ldap_value_free(v); /* cleanup */
176:
177: return(ret);
178: }
179:
180: /*
181: * Walk through search results and return TRUE if we have a runas match,
182: * else FALSE.
183: * Since the runas directive in /etc/sudoers is optional, so is sudoRunAs.
184: */
185: int
186: sudo_ldap_check_runas(ld, entry)
187: LDAP *ld;
188: LDAPMessage *entry;
189: {
190: char **v = NULL, **p = NULL;
191: int ret = FALSE;
192:
193: if (!entry)
194: return(ret);
195:
196: /* get the values from the entry */
197: v = ldap_get_values(ld, entry, "sudoRunAs");
198:
199: /*
200: * BUG:
201: *
202: * if runas is not specified on the command line, the only information
203: * as to which user to run as is in the runas_default option. We should
204: * check to see if we have the local option present. Unfortunately we
205: * don't parse these options until after this routine says yes or no.
206: * The query has already returned, so we could peek at the attribute
207: * values here though.
208: *
209: * For now just require users to always use -u option unless its set
210: * in the global defaults. This behaviour is no different than the global
211: * /etc/sudoers.
212: *
213: * Sigh - maybe add this feature later
214: *
215: */
216:
217: /*
218: * If there are no runas entries, match runas_default against
219: * what the user specified on the command line.
220: */
221: if (!v)
1.2 millert 222: ret = !strcasecmp(runas_pw->pw_name, def_runas_default);
1.1 millert 223:
224: /* walk through values returned, looking for a match */
225: for (p = v; p && *p && !ret; p++) {
1.2 millert 226: switch (*p[0]) {
227: case '+':
228: if (netgr_matches(*p, NULL, NULL, runas_pw->pw_name))
229: ret = TRUE;
230: break;
231: case '%':
232: if (usergr_matches(*p, runas_pw->pw_name, runas_pw))
233: ret = TRUE;
234: break;
235: case 'A':
236: if (strcmp(*p, "ALL") == 0) {
237: ret = TRUE;
238: break;
239: }
240: /* FALLTHROUGH */
241: default:
242: if (strcasecmp(*p, runas_pw->pw_name) == 0)
243: ret = TRUE;
244: break;
245: }
1.1 millert 246: DPRINTF(("ldap sudoRunAs '%s' ... %s", *p,
247: ret ? "MATCH!" : "not"), 2);
248: }
249:
250: if (v)
251: ldap_value_free(v); /* cleanup */
252:
253: return(ret);
254: }
255:
256: /*
257: * Walk through search results and return TRUE if we have a command match.
258: */
259: int
1.4 ! millert 260: sudo_ldap_check_command(ld, entry, setenv_implied)
1.1 millert 261: LDAP *ld;
262: LDAPMessage *entry;
1.4 ! millert 263: int *setenv_implied;
1.1 millert 264: {
265: char *allowed_cmnd, *allowed_args, **v = NULL, **p = NULL;
266: int foundbang, ret = FALSE;
267:
268: if (!entry)
269: return(ret);
270:
271: v = ldap_get_values(ld, entry, "sudoCommand");
272:
273: /* get_first_entry */
274: for (p = v; p && *p && ret >= 0; p++) {
275: /* Match against ALL ? */
1.4 ! millert 276: if (!strcmp(*p, "ALL")) {
1.1 millert 277: ret = TRUE;
1.4 ! millert 278: if (setenv_implied != NULL)
! 279: *setenv_implied = TRUE;
1.1 millert 280: DPRINTF(("ldap sudoCommand '%s' ... MATCH!", *p), 2);
281: continue;
282: }
283:
284: /* check for !command */
285: if (**p == '!') {
286: foundbang = TRUE;
287: allowed_cmnd = estrdup(1 + *p); /* !command */
288: } else {
289: foundbang = FALSE;
290: allowed_cmnd = estrdup(*p); /* command */
291: }
292:
293: /* split optional args away from command */
294: allowed_args = strchr(allowed_cmnd, ' ');
295: if (allowed_args)
296: *allowed_args++ = '\0';
297:
298: /* check the command like normal */
299: if (command_matches(allowed_cmnd, allowed_args)) {
300: /*
301: * If allowed (no bang) set ret but keep on checking.
302: * If disallowed (bang), exit loop.
303: */
304: ret = foundbang ? -1 : TRUE;
305: }
306: DPRINTF(("ldap sudoCommand '%s' ... %s", *p,
307: ret == TRUE ? "MATCH!" : "not"), 2);
308:
309: efree(allowed_cmnd); /* cleanup */
310: }
311:
312: if (v)
313: ldap_value_free(v); /* more cleanup */
314:
315: /* return TRUE if we found at least one ALLOW and no DENY */
316: return(ret > 0);
317: }
318:
319: /*
320: * Read sudoOption and modify the defaults as we go. This is used once
321: * from the cn=defaults entry and also once when a final sudoRole is matched.
322: */
323: void
324: sudo_ldap_parse_options(ld, entry)
325: LDAP *ld;
326: LDAPMessage *entry;
327: {
328: char op, *var, *val, **v = NULL, **p = NULL;
329:
330: if (!entry)
331: return;
332:
333: v = ldap_get_values(ld, entry, "sudoOption");
334:
335: /* walk through options */
336: for (p = v; p && *p; p++) {
337:
338: DPRINTF(("ldap sudoOption: '%s'", *p), 2);
339: var = estrdup(*p);
340:
341: /* check for equals sign past first char */
342: val = strchr(var, '=');
343: if (val > var) {
344: *val++ = '\0'; /* split on = and truncate var */
345: op = *(val - 2); /* peek for += or -= cases */
346: if (op == '+' || op == '-') {
347: *(val - 2) = '\0'; /* found, remove extra char */
348: /* case var+=val or var-=val */
349: set_default(var, val, (int) op);
350: } else {
351: /* case var=val */
352: set_default(var, val, TRUE);
353: }
354: } else if (*var == '!') {
355: /* case !var Boolean False */
356: set_default(var + 1, NULL, FALSE);
357: } else {
358: /* case var Boolean True */
359: set_default(var, NULL, TRUE);
360: }
361: efree(var);
362: }
363:
364: if (v)
365: ldap_value_free(v);
366: }
367:
368: /*
369: * Concatenate strings, dynamically growing them as necessary.
370: * Strings can be arbitrarily long and are allocated/reallocated on
371: * the fly. Make sure to free them when you are done.
372: *
373: * Usage:
374: *
375: * char *s=NULL;
376: * size_t sz;
377: *
378: * ncat(&s,&sz,"This ");
379: * ncat(&s,&sz,"is ");
380: * ncat(&s,&sz,"an ");
381: * ncat(&s,&sz,"arbitrarily ");
382: * ncat(&s,&sz,"long ");
383: * ncat(&s,&sz,"string!");
384: *
385: * printf("String Value='%s', but has %d bytes allocated\n",s,sz);
386: *
387: */
388: void
389: ncat(s, sz, src)
390: char **s;
391: size_t *sz;
392: char *src;
393: {
394: size_t nsz;
395:
396: /* handle initial alloc */
397: if (*s == NULL) {
398: *s = estrdup(src);
399: *sz = strlen(src) + 1;
400: return;
401: }
402: /* handle realloc */
403: nsz = strlen(*s) + strlen(src) + 1;
404: if (*sz < nsz)
405: *s = erealloc((void *) *s, *sz = nsz * 2);
406: strlcat(*s, src, *sz);
407: }
408:
409: /*
410: * builds together a filter to check against ldap
411: */
412: char *
413: sudo_ldap_build_pass1()
414: {
415: struct group *grp;
416: size_t sz;
417: char *b = NULL;
418: int i;
419:
420: /* global OR */
421: ncat(&b, &sz, "(|");
422:
423: /* build filter sudoUser=user_name */
424: ncat(&b, &sz, "(sudoUser=");
425: ncat(&b, &sz, user_name);
426: ncat(&b, &sz, ")");
427:
428: /* Append primary group */
429: grp = getgrgid(user_gid);
430: if (grp != NULL) {
431: ncat(&b, &sz, "(sudoUser=%");
432: ncat(&b, &sz, grp -> gr_name);
433: ncat(&b, &sz, ")");
434: }
435:
436: /* Append supplementary groups */
437: for (i = 0; i < user_ngroups; i++) {
438: if ((grp = getgrgid(user_groups[i])) != NULL) {
439: ncat(&b, &sz, "(sudoUser=%");
440: ncat(&b, &sz, grp -> gr_name);
441: ncat(&b, &sz, ")");
442: }
443: }
444:
445: /* Add ALL to list */
446: ncat(&b, &sz, "(sudoUser=ALL)");
447:
448: /* End of OR List */
449: ncat(&b, &sz, ")");
450:
451: return(b);
452: }
453:
454: /*
455: * Map yes/true/on to TRUE, no/false/off to FALSE, else -1
456: */
457: int
458: _atobool(s)
459: const char *s;
460: {
461: switch (*s) {
462: case 'y':
463: case 'Y':
464: if (strcasecmp(s, "yes") == 0)
465: return(TRUE);
466: break;
467: case 't':
468: case 'T':
469: if (strcasecmp(s, "true") == 0)
470: return(TRUE);
471: break;
472: case 'o':
473: case 'O':
474: if (strcasecmp(s, "on") == 0)
475: return(TRUE);
476: if (strcasecmp(s, "off") == 0)
477: return(FALSE);
478: break;
479: case 'n':
480: case 'N':
481: if (strcasecmp(s, "no") == 0)
482: return(FALSE);
483: break;
484: case 'f':
485: case 'F':
486: if (strcasecmp(s, "false") == 0)
487: return(FALSE);
488: break;
489: }
490: return(-1);
491: }
492:
493: int
494: sudo_ldap_read_config()
495: {
496: FILE *f;
497: char buf[LINE_MAX], *c, *keyword, *value;
498:
499: /* defaults */
500: ldap_conf.version = 3;
501: ldap_conf.port = 389;
502: ldap_conf.tls_checkpeer = -1;
503: ldap_conf.timelimit = -1;
504: ldap_conf.bind_timelimit = -1;
505:
506: if ((f = fopen(_PATH_LDAP_CONF, "r")) == NULL)
507: return(FALSE);
508: while (fgets(buf, sizeof(buf), f)) {
509: /* ignore text after comment character */
510: if ((c = strchr(buf, '#')) != NULL)
511: *c = '\0';
512:
513: /* skip leading whitespace */
514: for (c = buf; isspace((unsigned char) *c); c++)
515: /* nothing */;
516:
517: if (*c == '\0' || *c == '\n')
518: continue; /* skip empty line */
519:
520: /* properly terminate keyword string */
521: keyword = c;
522: while (*c && !isspace((unsigned char) *c))
523: c++;
524: if (*c)
525: *c++ = '\0'; /* terminate keyword */
526:
527: /* skip whitespace before value */
528: while (isspace((unsigned char) *c))
529: c++;
530: value = c;
531:
532: /* trim whitespace after value */
533: while (*c)
534: c++; /* wind to end */
535: while (--c > value && isspace((unsigned char) *c))
536: *c = '\0';
537:
538: /* The following macros make the code much more readable */
539:
540: #define MATCH_S(x,y) if (!strcasecmp(keyword,x)) \
541: { efree(y); y=estrdup(value); }
542: #define MATCH_I(x,y) if (!strcasecmp(keyword,x)) { y=atoi(value); }
543: #define MATCH_B(x,y) if (!strcasecmp(keyword,x)) { y=_atobool(value); }
544:
545: /*
546: * Parse values using a continues chain of if else if else if else if
547: * else ...
548: */
549: MATCH_S("host", ldap_conf.host)
550: else
551: MATCH_I("port", ldap_conf.port)
552: else
553: MATCH_S("ssl", ldap_conf.ssl)
554: else
555: MATCH_B("tls_checkpeer", ldap_conf.tls_checkpeer)
556: else
557: MATCH_S("tls_cacertfile", ldap_conf.tls_cacertfile)
558: else
559: MATCH_S("tls_cacertdir", ldap_conf.tls_cacertdir)
560: else
561: MATCH_S("tls_randfile", ldap_conf.tls_random_file)
562: else
563: MATCH_S("tls_ciphers", ldap_conf.tls_cipher_suite)
564: else
565: MATCH_S("tls_cert", ldap_conf.tls_certfile)
566: else
567: MATCH_S("tls_key", ldap_conf.tls_keyfile)
568: else
569: MATCH_I("ldap_version", ldap_conf.version)
570: else
571: MATCH_I("bind_timelimit", ldap_conf.bind_timelimit)
572: else
573: MATCH_I("timelimit", ldap_conf.timelimit)
574: else
575: MATCH_S("uri", ldap_conf.uri)
576: else
577: MATCH_S("binddn", ldap_conf.binddn)
578: else
579: MATCH_S("bindpw", ldap_conf.bindpw)
580: else
581: MATCH_S("rootbinddn", ldap_conf.rootbinddn)
582: else
583: MATCH_S("sudoers_base", ldap_conf.base)
584: else
585: MATCH_I("sudoers_debug", ldap_conf.debug)
586: else {
587:
588: /*
589: * The keyword was unrecognized. Since this config file is
590: * shared by multiple programs, it is appropriate to silently
591: * ignore options this program does not understand
592: */
593: }
594:
595: }
596: fclose(f);
597:
598: if (!ldap_conf.host)
599: ldap_conf.host = estrdup("localhost");
600:
601: if (ldap_conf.bind_timelimit > 0)
602: ldap_conf.bind_timelimit *= 1000; /* convert to ms */
603:
604: if (ldap_conf.debug > 1) {
605: fprintf(stderr, "LDAP Config Summary\n");
606: fprintf(stderr, "===================\n");
607: #ifdef HAVE_LDAP_INITIALIZE
608: if (ldap_conf.uri) {
609: fprintf(stderr, "uri %s\n", ldap_conf.uri);
610: } else
611: #endif
612: {
613: fprintf(stderr, "host %s\n", ldap_conf.host ?
614: ldap_conf.host : "(NONE)");
615: fprintf(stderr, "port %d\n", ldap_conf.port);
616: }
617: fprintf(stderr, "ldap_version %d\n", ldap_conf.version);
618:
619: fprintf(stderr, "sudoers_base %s\n", ldap_conf.base ?
620: ldap_conf.base : "(NONE) <---Sudo will ignore ldap)");
621: fprintf(stderr, "binddn %s\n", ldap_conf.binddn ?
622: ldap_conf.binddn : "(anonymous)");
623: fprintf(stderr, "bindpw %s\n", ldap_conf.bindpw ?
624: ldap_conf.bindpw : "(anonymous)");
625: fprintf(stderr, "bind_timelimit %d\n", ldap_conf.bind_timelimit);
626: fprintf(stderr, "timelimit %d\n", ldap_conf.timelimit);
627: #ifdef HAVE_LDAP_START_TLS_S
628: fprintf(stderr, "ssl %s\n", ldap_conf.ssl ?
629: ldap_conf.ssl : "(no)");
630: #endif
631: fprintf(stderr, "===================\n");
632: }
633: if (!ldap_conf.base)
634: return(FALSE); /* if no base is defined, ignore LDAP */
635:
636: /* If rootbinddn set, read in /etc/ldap.secret if it exists. */
637: if (ldap_conf.rootbinddn) {
638: if ((f = fopen(_PATH_LDAP_SECRET, "r")) != NULL) {
639: if (fgets(buf, sizeof(buf), f) != NULL) {
640: /* removing trailing newlines */
641: for (c = buf; *c != '\0'; c++)
642: continue;
643: while (--c > buf && *c == '\n')
644: *c = '\0';
645: /* copy to bindpw and binddn */
646: efree(ldap_conf.bindpw);
647: ldap_conf.bindpw = estrdup(buf);
648: efree(ldap_conf.binddn);
649: ldap_conf.binddn = ldap_conf.rootbinddn;
650: ldap_conf.rootbinddn = NULL;
651: }
652: fclose(f);
653: }
654: }
655: return(TRUE);
656: }
657:
658: /*
659: * like perl's join(sep,@ARGS)
660: */
661: char *
662: _ldap_join_values(sep, v)
663: char *sep;
664: char **v;
665: {
666: char *b = NULL, **p = NULL;
667: size_t sz = 0;
668:
669: /* paste values together */
670: for (p = v; p && *p; p++) {
671: if (p != v && sep != NULL)
1.3 martynas 672: ncat(&b, &sz, sep); /* append separator */
1.1 millert 673: ncat(&b, &sz, *p); /* append value */
674: }
675:
676: /* sanity check */
677: if (b[0] == '\0') {
678: /* something went wrong, put something here */
679: ncat(&b, &sz, "(empty list)"); /* append value */
680: }
681:
682: return(b);
683: }
684:
685: char *sudo_ldap_cm_list = NULL;
686: size_t sudo_ldap_cm_list_size;
687:
688: #define SAVE_LIST(x) ncat(&sudo_ldap_cm_list,&sudo_ldap_cm_list_size,(x))
689: /*
690: * Walks through search result and returns TRUE if we have a
691: * command match
692: */
693: int
694: sudo_ldap_add_match(ld, entry, pwflag)
695: LDAP *ld;
696: LDAPMessage *entry;
697: int pwflag;
698: {
699: char *dn, **edn, **v = NULL;
700:
701: /* if we are not collecting matches, then don't save them */
702: if (pwflag != I_LISTPW)
703: return(TRUE);
704:
705: /* collect the dn, only show the rdn */
706: dn = ldap_get_dn(ld, entry);
707: edn = dn ? ldap_explode_dn(dn, 1) : NULL;
708: SAVE_LIST("\nLDAP Role: ");
709: SAVE_LIST((edn && *edn) ? *edn : "UNKNOWN");
710: SAVE_LIST("\n");
711: if (dn)
712: ldap_memfree(dn);
713: if (edn)
714: ldap_value_free(edn);
715:
716: /* get the Runas Values from the entry */
717: v = ldap_get_values(ld, entry, "sudoRunAs");
718: if (v && *v) {
719: SAVE_LIST(" RunAs: (");
720: SAVE_LIST(_ldap_join_values(", ", v));
721: SAVE_LIST(")\n");
722: }
723: if (v)
724: ldap_value_free(v);
725:
726: /* get the Command Values from the entry */
727: v = ldap_get_values(ld, entry, "sudoCommand");
728: if (v && *v) {
729: SAVE_LIST(" Commands:\n ");
730: SAVE_LIST(_ldap_join_values("\n ", v));
731: SAVE_LIST("\n");
732: } else {
733: SAVE_LIST(" Commands: NONE\n");
734: }
735: if (v)
736: ldap_value_free(v);
737:
738: return(FALSE); /* Don't stop at the first match */
739: }
740: #undef SAVE_LIST
741:
742: void
743: sudo_ldap_list_matches()
744: {
745: if (sudo_ldap_cm_list != NULL)
746: printf("%s", sudo_ldap_cm_list);
747: }
748:
749: /* macros to set option, error on failure plus consistent debugging */
750: #define SET_OPTS(opt, val) do { \
751: if (ldap_conf.val != NULL) { \
752: if (ldap_conf.debug > 1) \
753: fprintf(stderr, \
754: "ldap_set_option(LDAP_OPT_%s, \"%s\")\n", #opt, ldap_conf.val);\
755: rc = ldap_set_option(ld, LDAP_OPT_ ## opt, ldap_conf.val); \
756: if (rc != LDAP_OPT_SUCCESS) { \
757: fprintf(stderr,"ldap_set_option(LDAP_OPT_%s, \"%s\")=%d: %s\n", \
758: #opt, ldap_conf.val, rc, ldap_err2string(rc)); \
759: return(NULL); \
760: } \
761: } \
762: } while(0)
763: #define SET_OPTI(opt, val) do { \
764: if (ldap_conf.val >= 0) { \
765: if (ldap_conf.debug > 1) \
766: fprintf(stderr, \
767: "ldap_set_option(LDAP_OPT_%s, %d)\n", #opt, ldap_conf.val); \
768: rc = ldap_set_option(ld, LDAP_OPT_ ## opt, &ldap_conf.val); \
769: if (rc != LDAP_OPT_SUCCESS) { \
770: fprintf(stderr,"ldap_set_option(LDAP_OPT_%s, %d)=%d: %s\n", \
771: #opt, ldap_conf.val, rc, ldap_err2string(rc)); \
772: return(NULL); \
773: } \
774: } \
775: } while(0)
776:
777: /*
778: * Open a connection to the LDAP server.
779: */
780: static LDAP *
781: sudo_ldap_open()
782: {
783: LDAP *ld = NULL;
784: int rc;
785:
786: if (!sudo_ldap_read_config())
787: return(NULL);
788:
789: /* attempt to setup ssl options */
790: #ifdef LDAP_OPT_X_TLS_CACERTFILE
791: SET_OPTS(X_TLS_CACERTFILE, tls_cacertfile);
792: #endif /* LDAP_OPT_X_TLS_CACERTFILE */
793:
794: #ifdef LDAP_OPT_X_TLS_CACERTDIR
795: SET_OPTS(X_TLS_CACERTDIR, tls_cacertdir);
796: #endif /* LDAP_OPT_X_TLS_CACERTDIR */
797:
798: #ifdef LDAP_OPT_X_TLS_CERTFILE
799: SET_OPTS(X_TLS_CERTFILE, tls_certfile);
800: #endif /* LDAP_OPT_X_TLS_CERTFILE */
801:
802: #ifdef LDAP_OPT_X_TLS_KEYFILE
803: SET_OPTS(X_TLS_KEYFILE, tls_keyfile);
804: #endif /* LDAP_OPT_X_TLS_KEYFILE */
805:
806: #ifdef LDAP_OPT_X_TLS_CIPHER_SUITE
807: SET_OPTS(X_TLS_CIPHER_SUITE, tls_cipher_suite);
808: #endif /* LDAP_OPT_X_TLS_CIPHER_SUITE */
809:
810: #ifdef LDAP_OPT_X_TLS_RANDOM_FILE
811: SET_OPTS(X_TLS_RANDOM_FILE, tls_random_file);
812: #endif /* LDAP_OPT_X_TLS_RANDOM_FILE */
813:
814: #ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
815: /* check the server certificate? */
816: SET_OPTI(X_TLS_REQUIRE_CERT, tls_checkpeer);
817: #endif /* LDAP_OPT_X_TLS_REQUIRE_CERT */
818:
819: /* set timelimit options */
820: SET_OPTI(TIMELIMIT, timelimit);
821:
822: #ifdef LDAP_OPT_NETWORK_TIMEOUT
823: if (ldap_conf.bind_timelimit > 0) {
824: struct timeval tv;
825: tv.tv_sec = ldap_conf.bind_timelimit / 1000;
826: tv.tv_usec = 0;
827: if (ldap_conf.debug > 1)
828: fprintf(stderr, "ldap_set_option(LDAP_OPT_NETWORK_TIMEOUT, %ld)\n",
829: tv.tv_sec);
830: rc = ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &tv);
831: if (rc != LDAP_OPT_SUCCESS) {
832: fprintf(stderr,"ldap_set_option(NETWORK_TIMEOUT, %ld)=%d: %s\n",
833: tv.tv_sec, rc, ldap_err2string(rc));
834: return(NULL);
835: }
836: }
837: #endif
838:
839: /* attempt connect */
840: #ifdef HAVE_LDAP_INITIALIZE
841: if (ldap_conf.uri) {
842:
843: DPRINTF(("ldap_initialize(ld,%s)", ldap_conf.uri), 2);
844:
845: rc = ldap_initialize(&ld, ldap_conf.uri);
846: if (rc) {
847: fprintf(stderr, "ldap_initialize()=%d : %s\n",
848: rc, ldap_err2string(rc));
849: return(NULL);
850: }
851: } else
852: #endif /* HAVE_LDAP_INITIALIZE */
853: if (ldap_conf.host) {
854:
855: DPRINTF(("ldap_init(%s,%d)", ldap_conf.host, ldap_conf.port), 2);
856:
857: if ((ld = ldap_init(ldap_conf.host, ldap_conf.port)) == NULL) {
858: fprintf(stderr, "ldap_init(): errno=%d : %s\n",
859: errno, strerror(errno));
860: return(NULL);
861: }
862: }
863: #ifdef LDAP_OPT_PROTOCOL_VERSION
864:
865: /* Set the LDAP Protocol version */
866: SET_OPTI(PROTOCOL_VERSION, version);
867:
868: #endif /* LDAP_OPT_PROTOCOL_VERSION */
869:
870: #ifdef HAVE_LDAP_START_TLS_S
871: /* Turn on TLS */
872: if (ldap_conf.ssl && !strcasecmp(ldap_conf.ssl, "start_tls")) {
873: rc = ldap_start_tls_s(ld, NULL, NULL);
874: if (rc != LDAP_SUCCESS) {
875: fprintf(stderr, "ldap_start_tls_s(): %d: %s\n", rc,
876: ldap_err2string(rc));
877: ldap_unbind(ld);
878: return(NULL);
879: }
880: DPRINTF(("ldap_start_tls_s() ok"), 1);
881: }
882: #endif /* HAVE_LDAP_START_TLS_S */
883:
884: /* Actually connect */
885: if ((rc = ldap_simple_bind_s(ld, ldap_conf.binddn, ldap_conf.bindpw))) {
886: fprintf(stderr, "ldap_simple_bind_s()=%d : %s\n",
887: rc, ldap_err2string(rc));
888: return(NULL);
889: }
890: DPRINTF(("ldap_bind() ok"), 1);
891:
892: return(ld);
893: }
894:
895: static void
896: sudo_ldap_update_defaults(ld)
897: LDAP *ld;
898: {
899: LDAPMessage *entry = NULL, *result = NULL; /* used for searches */
900: int rc; /* temp return value */
901:
902: rc = ldap_search_s(ld, ldap_conf.base, LDAP_SCOPE_SUBTREE,
903: "cn=defaults", NULL, 0, &result);
904: if (!rc && (entry = ldap_first_entry(ld, result))) {
905: DPRINTF(("found:%s", ldap_get_dn(ld, entry)), 1);
906: sudo_ldap_parse_options(ld, entry);
907: } else
908: DPRINTF(("no default options found!"), 1);
909:
910: if (result)
911: ldap_msgfree(result);
912: }
913:
914: /*
915: * like sudoers_lookup() - only LDAP style
916: */
917: int
918: sudo_ldap_check(pwflag)
919: int pwflag;
920: {
921: LDAP *ld;
922: LDAPMessage *entry = NULL, *result = NULL; /* used for searches */
923: char *filt; /* used to parse attributes */
924: int rc, ret = FALSE, do_netgr; /* temp/final return values */
1.4 ! millert 925: int setenv_implied;
1.1 millert 926: int ldap_user_matches = FALSE, ldap_host_matches = FALSE; /* flags */
927:
928: /* Open a connection to the LDAP server. */
929: if ((ld = sudo_ldap_open()) == NULL)
930: return(VALIDATE_ERROR);
931:
932: /* Parse Default options. */
933: sudo_ldap_update_defaults(ld);
934:
935: /*
936: * Okay - time to search for anything that matches this user
937: * Lets limit it to only two queries of the LDAP server
938: *
939: * The first pass will look by the username, groups, and
940: * the keyword ALL. We will then inspect the results that
941: * came back from the query. We don't need to inspect the
942: * sudoUser in this pass since the LDAP server already scanned
943: * it for us.
944: *
945: * The second pass will return all the entries that contain
946: * user netgroups. Then we take the netgroups returned and
947: * try to match them against the username.
948: */
1.4 ! millert 949: setenv_implied = FALSE;
1.1 millert 950: for (do_netgr = 0; !ret && do_netgr < 2; do_netgr++) {
951: filt = do_netgr ? estrdup("sudoUser=+*") : sudo_ldap_build_pass1();
952: DPRINTF(("ldap search '%s'", filt), 1);
953: rc = ldap_search_s(ld, ldap_conf.base, LDAP_SCOPE_SUBTREE, filt,
954: NULL, 0, &result);
955: if (rc)
956: DPRINTF(("nothing found for '%s'", filt), 1);
957: efree(filt);
958:
959: /* parse each entry returned from this most recent search */
960: entry = rc ? NULL : ldap_first_entry(ld, result);
961: while (entry != NULL) {
962: DPRINTF(("found:%s", ldap_get_dn(ld, entry)), 1);
963: if (
964: /* first verify user netgroup matches - only if in pass 2 */
965: (!do_netgr || sudo_ldap_check_user_netgroup(ld, entry)) &&
966: /* remember that user matched */
967: (ldap_user_matches = -1) &&
968: /* verify host match */
969: sudo_ldap_check_host(ld, entry) &&
970: /* remember that host matched */
971: (ldap_host_matches = -1) &&
972: /* add matches for listing later */
973: sudo_ldap_add_match(ld, entry, pwflag) &&
974: /* verify command match */
1.4 ! millert 975: sudo_ldap_check_command(ld, entry, &setenv_implied) &&
1.1 millert 976: /* verify runas match */
977: sudo_ldap_check_runas(ld, entry)
978: ) {
979: /* We have a match! */
980: DPRINTF(("Perfect Matched!"), 1);
981: /* pick up any options */
1.4 ! millert 982: if (setenv_implied)
! 983: def_setenv = TRUE;
1.1 millert 984: sudo_ldap_parse_options(ld, entry);
985: /* make sure we don't reenter loop */
986: ret = VALIDATE_OK;
987: /* break from inside for loop */
988: break;
989: }
990: entry = ldap_next_entry(ld, entry);
991: }
992: if (result)
993: ldap_msgfree(result);
994: result = NULL;
995: }
996:
997: sudo_ldap_close(ld); /* shut down connection */
998:
999: DPRINTF(("user_matches=%d", ldap_user_matches), 1);
1000: DPRINTF(("host_matches=%d", ldap_host_matches), 1);
1001:
1002: /* Check for special case for -v, -k, -l options */
1003: if (pwflag && ldap_user_matches && ldap_host_matches) {
1004: /*
1005: * Handle verifypw & listpw
1006: *
1007: * To be extra paranoid, since we haven't read any NOPASSWD options
1008: * in /etc/sudoers yet, but we have to make the decission now, lets
1009: * assume the worst and prefer to prompt for password unless the setting
1010: * is "never". (example verifypw=never or listpw=never)
1011: *
1012: */
1013: ret = VALIDATE_OK;
1014: if (pwflag == -1) {
1015: SET(ret, FLAG_NOPASS); /* -k or -K */
1016: } else {
1017: switch (sudo_defs_table[pwflag].sd_un.tuple) {
1018: case never:
1019: SET(ret, FLAG_NOPASS);
1020: break;
1021: case always:
1022: if (def_authenticate)
1023: SET(ret, FLAG_CHECK_USER);
1024: break;
1025: default:
1026: break;
1027: }
1028: }
1029: }
1030: if (ISSET(ret, VALIDATE_OK)) {
1031: /* we have a match, should we check the password? */
1032: if (!def_authenticate)
1033: SET(ret, FLAG_NOPASS);
1034: if (def_noexec)
1035: SET(ret, FLAG_NOEXEC);
1036: if (def_setenv)
1037: SET(ret, FLAG_SETENV);
1038: } else {
1039: /* we do not have a match */
1040: ret = VALIDATE_NOT_OK;
1041: if (pwflag)
1042: SET(ret, FLAG_NO_CHECK);
1043: else if (!ldap_user_matches)
1044: SET(ret, FLAG_NO_USER);
1045: else if (!ldap_host_matches)
1046: SET(ret, FLAG_NO_HOST);
1047: }
1048: DPRINTF(("sudo_ldap_check(%d)=0x%02x", pwflag, ret), 1);
1049:
1050: return(ret);
1051: }
1052:
1053: /*
1054: * shut down LDAP connection
1055: */
1056: static void
1057: sudo_ldap_close(LDAP *ld)
1058: {
1059: if (ld)
1060: ldap_unbind_s(ld);
1061: }