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