X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=ldap.c;h=33e65067c163e123f9a74ec13ee66e00f714cbe0;hb=abffa756f90bac83386a0677f18518136710b22a;hp=838587df80c55f8b89a3d2b7e6c65fbdeb9e1d49;hpb=26b91e48d9297ef94e92d8501f12b1516096a940;p=debian%2Fsudo diff --git a/ldap.c b/ldap.c index 838587d..33e6506 100644 --- a/ldap.c +++ b/ldap.c @@ -1,5 +1,7 @@ /* - * Copyright (c) 1996, 1998-2004 Todd C. Miller + * Copyright (c) 2003-2009 Todd C. Miller + * + * This code is derived from software contributed by Aaron Spangler. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,9 +16,10 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "config.h" +#include #include +#include #include #include #include @@ -47,915 +50,1900 @@ #include #include #include -#include #ifdef HAVE_LBER_H -#include +# include #endif #include +#if defined(HAVE_LDAP_SSL_H) +# include +#elif defined(HAVE_MPS_LDAP_SSL_H) +# include +#endif +#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S +# ifdef HAVE_SASL_SASL_H +# include +# else +# include +# endif +# if HAVE_GSS_KRB5_CCACHE_NAME +# if defined(HAVE_GSSAPI_GSSAPI_KRB5_H) +# include +# include +# elif defined(HAVE_GSSAPI_GSSAPI_H) +# include +# else +# include +# endif +# endif +#endif #include "sudo.h" #include "parse.h" +#include "lbuf.h" #ifndef lint -static const char rcsid[] = "$Sudo: ldap.c,v 1.14 2004/09/02 04:03:25 aaron Exp $"; +__unused static const char rcsid[] = "$Sudo: ldap.c,v 1.108 2009/05/29 13:43:12 millert Exp $"; #endif /* lint */ -/* LDAP code below */ +#ifndef LDAP_OPT_SUCCESS +# define LDAP_OPT_SUCCESS LDAP_SUCCESS +#endif + +#ifndef LDAPS_PORT +# define LDAPS_PORT 636 +#endif -#ifndef BUF_SIZ -#define BUF_SIZ 1024 +#if defined(HAVE_LDAP_SASL_INTERACTIVE_BIND_S) && !defined(LDAP_SASL_QUIET) +# define LDAP_SASL_QUIET 0 #endif -extern int printmatches; +#ifndef HAVE_LDAP_UNBIND_EXT_S +#define ldap_unbind_ext_s(a, b, c) ldap_unbind_s(a) +#endif + +#ifndef HAVE_LDAP_SEARCH_EXT_S +#define ldap_search_ext_s(a, b, c, d, e, f, g, h, i, j, k) \ + ldap_search_s(a, b, c, d, e, f, k) +#endif + +#define LDAP_FOREACH(var, ld, res) \ + for ((var) = ldap_first_entry((ld), (res)); \ + (var) != NULL; \ + (var) = ldap_next_entry((ld), (var))) + +#define DPRINTF(args, level) if (ldap_conf.debug >= level) warningx args + +#define CONF_BOOL 0 +#define CONF_INT 1 +#define CONF_STR 2 + +#define SUDO_LDAP_SSL 1 +#define SUDO_LDAP_STARTTLS 2 + +struct ldap_config_table { + const char *conf_str; /* config file string */ + short type; /* CONF_BOOL, CONF_INT, CONF_STR */ + short connected; /* connection-specific value? */ + int opt_val; /* LDAP_OPT_* (or -1 for sudo internal) */ + void *valp; /* pointer into ldap_conf */ +}; /* ldap configuration structure */ -struct ldap_config { - char *host; - int port; - int version; - char *uri; - char *binddn; - char *bindpw; - char *base; - char *ssl; - int tls_checkpeer; - char *tls_cacertfile; - char *tls_cacertdir; - char *tls_random_file; - char *tls_cipher_suite; - char *tls_certfile; - char *tls_keyfile; - int debug; +static struct ldap_config { + int port; + int version; + int debug; + int ldap_debug; + int tls_checkpeer; + int timelimit; + int bind_timelimit; + int use_sasl; + int rootuse_sasl; + int ssl_mode; + char *host; + char *uri; + char *binddn; + char *bindpw; + char *rootbinddn; + char *base; + char *ssl; + char *tls_cacertfile; + char *tls_cacertdir; + char *tls_random_file; + char *tls_cipher_suite; + char *tls_certfile; + char *tls_keyfile; + char *sasl_auth_id; + char *rootsasl_auth_id; + char *sasl_secprops; + char *krb5_ccname; } ldap_conf; +static struct ldap_config_table ldap_conf_table[] = { + { "sudoers_debug", CONF_INT, FALSE, -1, &ldap_conf.debug }, + { "host", CONF_STR, FALSE, -1, &ldap_conf.host }, + { "port", CONF_INT, FALSE, -1, &ldap_conf.port }, + { "ssl", CONF_STR, FALSE, -1, &ldap_conf.ssl }, + { "sslpath", CONF_STR, FALSE, -1, &ldap_conf.tls_certfile }, + { "uri", CONF_STR, FALSE, -1, &ldap_conf.uri }, +#ifdef LDAP_OPT_DEBUG_LEVEL + { "debug", CONF_INT, FALSE, LDAP_OPT_DEBUG_LEVEL, &ldap_conf.ldap_debug }, +#endif +#ifdef LDAP_OPT_PROTOCOL_VERSION + { "ldap_version", CONF_INT, TRUE, LDAP_OPT_PROTOCOL_VERSION, + &ldap_conf.version }, +#endif +#ifdef LDAP_OPT_X_TLS_REQUIRE_CERT + { "tls_checkpeer", CONF_BOOL, FALSE, LDAP_OPT_X_TLS_REQUIRE_CERT, + &ldap_conf.tls_checkpeer }, +#else + { "tls_checkpeer", CONF_BOOL, FALSE, -1, &ldap_conf.tls_checkpeer }, +#endif +#ifdef LDAP_OPT_X_TLS_CACERTFILE + { "tls_cacertfile", CONF_STR, FALSE, LDAP_OPT_X_TLS_CACERTFILE, + &ldap_conf.tls_cacertfile }, +#endif +#ifdef LDAP_OPT_X_TLS_CACERTDIR + { "tls_cacertdir", CONF_STR, FALSE, LDAP_OPT_X_TLS_CACERTDIR, + &ldap_conf.tls_cacertdir }, +#endif +#ifdef LDAP_OPT_X_TLS_RANDOM_FILE + { "tls_randfile", CONF_STR, FALSE, LDAP_OPT_X_TLS_RANDOM_FILE, + &ldap_conf.tls_random_file }, +#endif +#ifdef LDAP_OPT_X_TLS_CIPHER_SUITE + { "tls_ciphers", CONF_STR, FALSE, LDAP_OPT_X_TLS_CIPHER_SUITE, + &ldap_conf.tls_cipher_suite }, +#endif +#ifdef LDAP_OPT_X_TLS_CERTFILE + { "tls_cert", CONF_STR, FALSE, LDAP_OPT_X_TLS_CERTFILE, + &ldap_conf.tls_certfile }, +#else + { "tls_cert", CONF_STR, FALSE, -1, &ldap_conf.tls_certfile }, +#endif +#ifdef LDAP_OPT_X_TLS_KEYFILE + { "tls_key", CONF_STR, FALSE, LDAP_OPT_X_TLS_KEYFILE, + &ldap_conf.tls_keyfile }, +#else + { "tls_key", CONF_STR, FALSE, -1, &ldap_conf.tls_keyfile }, +#endif +#ifdef LDAP_OPT_NETWORK_TIMEOUT + { "bind_timelimit", CONF_INT, TRUE, -1 /* needs timeval, set manually */, + &ldap_conf.bind_timelimit }, +#elif defined(LDAP_X_OPT_CONNECT_TIMEOUT) + { "bind_timelimit", CONF_INT, TRUE, LDAP_X_OPT_CONNECT_TIMEOUT, + &ldap_conf.bind_timelimit }, +#endif + { "timelimit", CONF_INT, TRUE, LDAP_OPT_TIMELIMIT, &ldap_conf.timelimit }, + { "binddn", CONF_STR, FALSE, -1, &ldap_conf.binddn }, + { "bindpw", CONF_STR, FALSE, -1, &ldap_conf.bindpw }, + { "rootbinddn", CONF_STR, FALSE, -1, &ldap_conf.rootbinddn }, + { "sudoers_base", CONF_STR, FALSE, -1, &ldap_conf.base }, +#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S + { "use_sasl", CONF_BOOL, FALSE, -1, &ldap_conf.use_sasl }, + { "sasl_auth_id", CONF_STR, FALSE, -1, &ldap_conf.sasl_auth_id }, + { "rootuse_sasl", CONF_BOOL, FALSE, -1, &ldap_conf.rootuse_sasl }, + { "rootsasl_auth_id", CONF_STR, FALSE, -1, &ldap_conf.rootsasl_auth_id }, +# ifdef LDAP_OPT_X_SASL_SECPROPS + { "sasl_secprops", CONF_STR, TRUE, LDAP_OPT_X_SASL_SECPROPS, + &ldap_conf.sasl_secprops }, +# endif + { "krb5_ccname", CONF_STR, FALSE, -1, &ldap_conf.krb5_ccname }, +#endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */ + { NULL } +}; + +struct sudo_nss sudo_nss_ldap = { + &sudo_nss_ldap, + NULL, + sudo_ldap_open, + sudo_ldap_close, + sudo_ldap_parse, + sudo_ldap_setdefs, + sudo_ldap_lookup, + sudo_ldap_display_cmnd, + sudo_ldap_display_defaults, + sudo_ldap_display_bound_defaults, + sudo_ldap_display_privs +}; + +#ifdef HAVE_LDAP_CREATE /* - * Walks through search result and returns true if we have a - * netgroup that matches our user + * Rebuild the hosts list and include a specific port for each host. + * ldap_create() does not take a default port parameter so we must + * append one if we want something other than LDAP_PORT. */ +static void +sudo_ldap_conf_add_ports() +{ + char *host, *port, defport[13]; + char hostbuf[LINE_MAX * 2]; + + hostbuf[0] = '\0'; + if (snprintf(defport, sizeof(defport), ":%d", ldap_conf.port) >= sizeof(defport)) + errorx(1, "sudo_ldap_conf_add_ports: port too large"); + + for ((host = strtok(ldap_conf.host, " \t")); host; (host = strtok(NULL, " \t"))) { + if (hostbuf[0] != '\0') { + if (strlcat(hostbuf, " ", sizeof(hostbuf)) >= sizeof(hostbuf)) + goto toobig; + } + + if (strlcat(hostbuf, host, sizeof(hostbuf)) >= sizeof(hostbuf)) + goto toobig; + /* Append port if there is not one already. */ + if ((port = strrchr(host, ':')) == NULL || !isdigit(port[1])) { + if (strlcat(hostbuf, defport, sizeof(hostbuf)) >= sizeof(hostbuf)) + goto toobig; + } + } -int -sudo_ldap_check_user_netgroup(ld,entry) - LDAP *ld; - LDAPMessage *entry; -{ - char **v=NULL; - char **p=NULL; + free(ldap_conf.host); + ldap_conf.host = estrdup(hostbuf); + return; - int ret=0; +toobig: + errorx(1, "sudo_ldap_conf_add_ports: out of space expanding hostbuf"); +} +#endif - if (!entry) return ret; +#ifndef HAVE_LDAP_INITIALIZE +/* + * For each uri, convert to host:port pairs. For ldaps:// enable SSL + * Accepts: uris of the form ldap:/// or ldap://hostname:portnum/ + * where the trailing slash is optional. + */ +static int +sudo_ldap_parse_uri(uri_list) + const char *uri_list; +{ + char *buf, *uri, *host, *cp, *port; + char hostbuf[LINE_MAX]; + int nldap = 0, nldaps = 0; + int rc = -1; + + buf = estrdup(uri_list); + hostbuf[0] = '\0'; + for ((uri = strtok(buf, " \t")); uri != NULL; (uri = strtok(NULL, " \t"))) { + if (strncasecmp(uri, "ldap://", 7) == 0) { + nldap++; + host = uri + 7; + } else if (strncasecmp(uri, "ldaps://", 8) == 0) { + nldaps++; + host = uri + 8; + } else { + warningx("unsupported LDAP uri type: %s", uri); + goto done; + } + + /* trim optional trailing slash */ + if ((cp = strrchr(host, '/')) != NULL && cp[1] == '\0') { + *cp = '\0'; + } + + if (hostbuf[0] != '\0') { + if (strlcat(hostbuf, " ", sizeof(hostbuf)) >= sizeof(hostbuf)) + goto toobig; + } + + if (*host == '\0') + host = "localhost"; /* no host specified, use localhost */ + + if (strlcat(hostbuf, host, sizeof(hostbuf)) >= sizeof(hostbuf)) + goto toobig; + + /* If using SSL and no port specified, add port 636 */ + if (nldaps) { + if ((port = strrchr(host, ':')) == NULL || !isdigit(port[1])) + if (strlcat(hostbuf, ":636", sizeof(hostbuf)) >= sizeof(hostbuf)) + goto toobig; + } + } + if (hostbuf[0] == '\0') { + warningx("invalid uri: %s", uri_list); + goto done; + } - /* get the values from the entry */ - v=ldap_get_values(ld,entry,"sudoUser"); + if (nldaps != 0) { + if (nldap != 0) { + warningx("cannot mix ldap and ldaps URIs"); + goto done; + } + if (ldap_conf.ssl_mode == SUDO_LDAP_STARTTLS) { + warningx("cannot mix ldaps and starttls"); + goto done; + } + ldap_conf.ssl_mode = SUDO_LDAP_SSL; + } - /* walk through values */ - for (p=v; p && *p && !ret;p++) - { - if (ldap_conf.debug>1) printf("ldap sudoUser netgroup '%s' ...",*p); + free(ldap_conf.host); + ldap_conf.host = estrdup(hostbuf); + rc = 0; - /* match any */ - if (netgr_matches(*p,NULL,NULL,user_name)) ret=1; +done: + efree(buf); + return(rc); - if (ldap_conf.debug>1) printf(" %s\n",ret ? "MATCH!" : "not"); - } +toobig: + errorx(1, "sudo_ldap_parse_uri: out of space building hostbuf"); +} +#endif /* HAVE_LDAP_INITIALIZE */ - /* cleanup */ - if (v) ldap_value_free(v); +static int +sudo_ldap_init(ldp, host, port) + LDAP **ldp; + const char *host; + int port; +{ + LDAP *ld = NULL; + int rc = LDAP_CONNECT_ERROR; + +#ifdef HAVE_LDAPSSL_INIT + if (ldap_conf.ssl_mode == SUDO_LDAP_SSL) { + DPRINTF(("ldapssl_clientauth_init(%s, %s)", + ldap_conf.tls_certfile ? ldap_conf.tls_certfile : "NULL", + ldap_conf.tls_keyfile ? ldap_conf.tls_keyfile : "NULL"), 2); + rc = ldapssl_clientauth_init(ldap_conf.tls_certfile, NULL, + ldap_conf.tls_keyfile != NULL, ldap_conf.tls_keyfile, NULL); + /* + * Mozilla-derived SDKs have a bug starting with version 5.0 + * where the path can no longer be a file name and must be a dir. + */ + if (rc != LDAP_SUCCESS) { + char *cp; + if (ldap_conf.tls_certfile) { + cp = strrchr(ldap_conf.tls_certfile, '/'); + if (cp != NULL && strncmp(cp + 1, "cert", 4) == 0) + *cp = '\0'; + } + if (ldap_conf.tls_keyfile) { + cp = strrchr(ldap_conf.tls_keyfile, '/'); + if (cp != NULL && strncmp(cp + 1, "key", 3) == 0) + *cp = '\0'; + } + DPRINTF(("ldapssl_clientauth_init(%s, %s)", + ldap_conf.tls_certfile ? ldap_conf.tls_certfile : "NULL", + ldap_conf.tls_keyfile ? ldap_conf.tls_keyfile : "NULL"), 2); + rc = ldapssl_clientauth_init(ldap_conf.tls_certfile, NULL, + ldap_conf.tls_keyfile != NULL, ldap_conf.tls_keyfile, NULL); + if (rc != LDAP_SUCCESS) { + warningx("unable to initialize SSL cert and key db: %s", + ldapssl_err2string(rc)); + goto done; + } + } + + DPRINTF(("ldapssl_init(%s, %d, 1)", host, port), 2); + if ((ld = ldapssl_init(host, port, 1)) != NULL) + rc = LDAP_SUCCESS; + } else +#endif + { +#ifdef HAVE_LDAP_CREATE + DPRINTF(("ldap_create()"), 2); + if ((rc = ldap_create(&ld)) != LDAP_SUCCESS) + goto done; + DPRINTF(("ldap_set_option(LDAP_OPT_HOST_NAME, %s)", host), 2); + rc = ldap_set_option(ld, LDAP_OPT_HOST_NAME, host); +#else + DPRINTF(("ldap_init(%s, %d)", host, port), 2); + if ((ld = ldap_init(host, port)) != NULL) + rc = LDAP_SUCCESS; +#endif + } - /* all done */ - return ret; +done: + *ldp = ld; + return(rc); } +/* + * Walk through search results and return TRUE if we have a matching + * netgroup, else FALSE. + */ +int +sudo_ldap_check_user_netgroup(ld, entry, user) + LDAP *ld; + LDAPMessage *entry; + char *user; +{ + struct berval **bv, **p; + char *val; + int ret = FALSE; + + if (!entry) + return(ret); + + /* get the values from the entry */ + bv = ldap_get_values_len(ld, entry, "sudoUser"); + if (bv == NULL) + return(ret); + + /* walk through values */ + for (p = bv; *p != NULL && !ret; p++) { + val = (*p)->bv_val; + /* match any */ + if (netgr_matches(val, NULL, NULL, user)) + ret = TRUE; + DPRINTF(("ldap sudoUser netgroup '%s' ... %s", val, + ret ? "MATCH!" : "not"), 2); + } + + ldap_value_free_len(bv); /* cleanup */ + + return(ret); +} /* - * Walks through search result and returns true if we have a - * host match + * Walk through search results and return TRUE if we have a + * host match, else FALSE. */ int -sudo_ldap_check_host(ld,entry) - LDAP *ld; - LDAPMessage *entry; +sudo_ldap_check_host(ld, entry) + LDAP *ld; + LDAPMessage *entry; { - char **v=NULL; - char **p=NULL; + struct berval **bv, **p; + char *val; + int ret = FALSE; + + if (!entry) + return(ret); + + /* get the values from the entry */ + bv = ldap_get_values_len(ld, entry, "sudoHost"); + if (bv == NULL) + return(ret); + + /* walk through values */ + for (p = bv; *p != NULL && !ret; p++) { + val = (*p)->bv_val; + /* match any or address or netgroup or hostname */ + if (!strcmp(val, "ALL") || addr_matches(val) || + netgr_matches(val, user_host, user_shost, NULL) || + hostname_matches(user_shost, user_host, val)) + ret = TRUE; + DPRINTF(("ldap sudoHost '%s' ... %s", val, + ret ? "MATCH!" : "not"), 2); + } + + ldap_value_free_len(bv); /* cleanup */ - int ret=0; + return(ret); +} - if (!entry) return ret; +int +sudo_ldap_check_runas_user(ld, entry) + LDAP *ld; + LDAPMessage *entry; +{ + struct berval **bv, **p; + char *val; + int ret = FALSE; - /* get the values from the entry */ - v=ldap_get_values(ld,entry,"sudoHost"); + if (!runas_pw) + return(UNSPEC); - /* walk through values */ - for (p=v; p && *p && !ret;p++) - { - if (ldap_conf.debug>1) printf("ldap sudoHost '%s' ...",*p); + /* get the runas user from the entry */ + bv = ldap_get_values_len(ld, entry, "sudoRunAsUser"); + if (bv == NULL) + bv = ldap_get_values_len(ld, entry, "sudoRunAs"); /* old style */ - /* match any or address or netgroup or hostname */ - if ( - !strcasecmp(*p,"ALL") || - addr_matches(*p) || - netgr_matches(*p,user_host,user_shost,NULL) || - !hostname_matches(user_shost,user_host,*p) - ) - { - ret=1; + /* + * BUG: + * + * if runas is not specified on the command line, the only information + * as to which user to run as is in the runas_default option. We should + * check to see if we have the local option present. Unfortunately we + * don't parse these options until after this routine says yes or no. + * The query has already returned, so we could peek at the attribute + * values here though. + * + * For now just require users to always use -u option unless its set + * in the global defaults. This behaviour is no different than the global + * /etc/sudoers. + * + * Sigh - maybe add this feature later + */ + + /* + * If there are no runas entries, match runas_default against + * what the user specified on the command line. + */ + if (bv == NULL) + return(!strcasecmp(runas_pw->pw_name, def_runas_default)); + + /* walk through values returned, looking for a match */ + for (p = bv; *p != NULL && !ret; p++) { + val = (*p)->bv_val; + switch (val[0]) { + case '+': + if (netgr_matches(val, NULL, NULL, runas_pw->pw_name)) + ret = TRUE; + break; + case '%': + if (usergr_matches(val, runas_pw->pw_name, runas_pw)) + ret = TRUE; + break; + case 'A': + if (strcmp(val, "ALL") == 0) { + ret = TRUE; + break; + } + /* FALLTHROUGH */ + default: + if (strcasecmp(val, runas_pw->pw_name) == 0) + ret = TRUE; + break; + } + DPRINTF(("ldap sudoRunAsUser '%s' ... %s", val, + ret ? "MATCH!" : "not"), 2); } + ldap_value_free_len(bv); /* cleanup */ - if (ldap_conf.debug>1) printf(" %s\n",ret ? "MATCH!" : "not"); - } + return(ret); +} - /* cleanup */ - if (v) ldap_value_free(v); +int +sudo_ldap_check_runas_group(ld, entry) + LDAP *ld; + LDAPMessage *entry; +{ + struct berval **bv, **p; + char *val; + int ret = FALSE; + + /* runas_gr is only set if the user specified the -g flag */ + if (!runas_gr) + return(UNSPEC); + + /* get the values from the entry */ + bv = ldap_get_values_len(ld, entry, "sudoRunAsGroup"); + if (bv == NULL) + return(ret); + + /* walk through values returned, looking for a match */ + for (p = bv; *p != NULL && !ret; p++) { + val = (*p)->bv_val; + if (strcmp(val, "ALL") == 0 || group_matches(val, runas_gr)) + ret = TRUE; + DPRINTF(("ldap sudoRunAsGroup '%s' ... %s", val, + ret ? "MATCH!" : "not"), 2); + } - /* all done */ - return ret; + ldap_value_free_len(bv); /* cleanup */ + + return(ret); } /* - * Walks through search result and returns true if we have a - * runas match. Since the runas directive in /etc/sudoers is optional, - * so is the sudoRunAs attribute. - * + * Walk through search results and return TRUE if we have a runas match, + * else FALSE. RunAs info is optional. */ - -int sudo_ldap_check_runas(ld,entry) - LDAP *ld; - LDAPMessage *entry; +int +sudo_ldap_check_runas(ld, entry) + LDAP *ld; + LDAPMessage *entry; { - char **v=NULL; - char **p=NULL; - - int ret=0; - - if (!entry) return ret; - - /* get the values from the entry */ - v=ldap_get_values(ld,entry,"sudoRunAs"); - - /* BUG: - * - * if runas is not specified on the command line, the only information as - * to which user to run as is in the runas_default option. - * We should check check to see if we have the local option present. - * Unfortunately we don't parse these options until after this routine - * says yes * or no. The query has already returned, so we could peek at the - * attribute values here though. - * - * For now just require users to always use -u option unless its set - * in the global defaults. This behaviour is no different than the global - * /etc/sudoers. - * - * Sigh - maybe add this feature later - * - */ - - /* If there are no runas entries, then match the runas_default with - * whats on the command line - */ - if (!v) - { - ret=!strcasecmp(*user_runas,def_runas_default); - } - - /* what about the case where exactly one runas is specified in - * the config and the user forgets the -u option, should we - * switch it? - Probably not - */ - - /* walk through values returned, looking for a match*/ - for (p=v; p && *p && !ret;p++) - { - if (ldap_conf.debug>1) printf("ldap sudoRunAs '%s' ...",*p); - - if ( - !strcasecmp(*p,*user_runas) || - !strcasecmp(*p,"ALL") - ) - { - ret = 1; - } + int ret; - if (ldap_conf.debug>1) printf(" %s\n",ret ? "MATCH!" : "not"); - } + if (!entry) + return(FALSE); - /* cleanup */ - if (v) ldap_value_free(v); + ret = sudo_ldap_check_runas_user(ld, entry) != FALSE && + sudo_ldap_check_runas_group(ld, entry) != FALSE; - /* all done */ - return ret; + return(ret); } /* - * Walks through search result and returns true if we have a - * command match + * Walk through search results and return TRUE if we have a command match, + * FALSE if disallowed and UNSPEC if not matched. */ -int sudo_ldap_check_command(ld,entry) - LDAP *ld; - LDAPMessage *entry; +int +sudo_ldap_check_command(ld, entry, setenv_implied) + LDAP *ld; + LDAPMessage *entry; + int *setenv_implied; { - char **v=NULL; - char **p=NULL; - char *allowed_cmnd; - char *allowed_args; - int ret=0; - int foundbang; - - if (!entry) return ret; - - v=ldap_get_values(ld,entry,"sudoCommand"); - - /* get_first_entry */ - for (p=v; p && *p && ret>=0;p++){ - if (ldap_conf.debug>1) printf("ldap sudoCommand '%s' ...",*p); - - /* Match against ALL ? */ - if (!strcasecmp(*p,"ALL")) { - ret=1; - if (safe_cmnd) free (safe_cmnd); - safe_cmnd=estrdup(user_cmnd); - if (ldap_conf.debug>1) printf(" MATCH!\n"); - continue; - } - - /* check for !command */ - if (**p != '!'){ - foundbang=0; - allowed_cmnd=estrdup(*p); /* command */ - } else { - foundbang=1; - allowed_cmnd=estrdup(1+*p); /* !command */ - } - - /* split optional args away from command */ - allowed_args=strchr(allowed_cmnd,' '); - if (allowed_args) *allowed_args++='\0'; - - /* check the command like normal */ - if (command_matches(allowed_cmnd,allowed_args)) { - if (!foundbang){ - ret=1; /* allowed, but keep checking for a deny match */ - } else { - ret=-1; /* denied by match, no need to check for more */ - } - if (ldap_conf.debug>1) printf(" MATCH!\n"); - } else { - if (ldap_conf.debug>1) printf(" not\n"); - } - - /* cleanup */ - free(allowed_cmnd); - } - - /* more cleanup */ - if (v) ldap_value_free(v); - - /* all done */ - return ret > 0; /* say true if we found at least one ALLOW and no DENY */ + struct berval **bv, **p; + char *allowed_cmnd, *allowed_args, *val; + int foundbang, ret = UNSPEC; + + if (!entry) + return(ret); + + bv = ldap_get_values_len(ld, entry, "sudoCommand"); + if (bv == NULL) + return(ret); + + for (p = bv; *p != NULL && ret != FALSE; p++) { + val = (*p)->bv_val; + /* Match against ALL ? */ + if (!strcmp(val, "ALL")) { + ret = TRUE; + if (setenv_implied != NULL) + *setenv_implied = TRUE; + DPRINTF(("ldap sudoCommand '%s' ... MATCH!", val), 2); + continue; + } + + /* check for !command */ + if (*val == '!') { + foundbang = TRUE; + allowed_cmnd = estrdup(1 + val); /* !command */ + } else { + foundbang = FALSE; + allowed_cmnd = estrdup(val); /* command */ + } + + /* split optional args away from command */ + allowed_args = strchr(allowed_cmnd, ' '); + if (allowed_args) + *allowed_args++ = '\0'; + + /* check the command like normal */ + if (command_matches(allowed_cmnd, allowed_args)) { + /* + * If allowed (no bang) set ret but keep on checking. + * If disallowed (bang), exit loop. + */ + ret = foundbang ? FALSE : TRUE; + } + DPRINTF(("ldap sudoCommand '%s' ... %s", val, + ret == TRUE ? "MATCH!" : "not"), 2); + + efree(allowed_cmnd); /* cleanup */ + } + + ldap_value_free_len(bv); /* more cleanup */ + + return(ret); } /* - * Read sudoOption, modify the defaults as we go. - * This is used once from the cn=defaults entry - * and also once when a final sudoRole is matched. - * + * Search for boolean "option" in sudoOption. + * Returns TRUE if found and allowed, FALSE if negated, else UNSPEC. */ -void -sudo_ldap_parse_options(ld,entry) - LDAP *ld; - LDAPMessage *entry; +int +sudo_ldap_check_bool(ld, entry, option) + LDAP *ld; + LDAPMessage *entry; + char *option; { - /* used to parse attributes */ - char **v=NULL; - char **p=NULL; - char *var; - char *val; - char op; - - if (!entry) return; - - v=ldap_get_values(ld,entry,"sudoOption"); - - /* walk through options */ - for (p=v; p && *p;p++){ - - if (ldap_conf.debug>1) printf("ldap sudoOption: '%s'\n",*p); - var=estrdup(*p); - /* check for = char */ - val=strchr(var,'='); - - /* check for equals sign past first char */ - if (val>var){ - *val++='\0'; /* split on = and truncate var */ - op=*(val-2); /* peek for += or -= cases */ - if (op == '+' || op == '-') { - *(val-2)='\0'; /* found, remove extra char */ - /* case var+=val or var-=val */ - set_default(var,val,(int)op); - } else { - /* case var=val */ - set_default(var,val,TRUE); - } - } else if (*var=='!'){ - /* case !var Boolean False */ - set_default(var+1,NULL,FALSE); - } else { - /* case var Boolean True */ - set_default(var,NULL,TRUE); - } - free(var); - - } - - if (v) ldap_value_free(v); + struct berval **bv, **p; + char ch, *var; + int ret = UNSPEC; + + if (entry == NULL) + return(UNSPEC); + + bv = ldap_get_values_len(ld, entry, "sudoOption"); + if (bv == NULL) + return(ret); + + /* walk through options */ + for (p = bv; *p != NULL; p++) { + var = (*p)->bv_val;; + DPRINTF(("ldap sudoOption: '%s'", var), 2); + + if ((ch = *var) == '!') + var++; + if (strcmp(var, option) == 0) + ret = (ch != '!'); + } + + ldap_value_free_len(bv); + return(ret); } /* - * Concatenate strings, dynamically growing them as necessary. - * Strings can be arbitrarily long and are allocated/reallocated on - * the fly. Make sure to free them when you are done. - * - * Usage: - * - * char *s=NULL; - * size_t sz; - * - * ncat(&s,&sz,"This "); - * ncat(&s,&sz,"is "); - * ncat(&s,&sz,"an "); - * ncat(&s,&sz,"arbitrarily "); - * ncat(&s,&sz,"long "); - * ncat(&s,&sz,"string!"); - * - * printf("String Value='%s', but has %d bytes allocated\n",s,sz); - * + * Read sudoOption and modify the defaults as we go. This is used once + * from the cn=defaults entry and also once when a final sudoRole is matched. */ void -ncat(s,sz,src) - char **s; - size_t *sz; - char *src; +sudo_ldap_parse_options(ld, entry) + LDAP *ld; + LDAPMessage *entry; { - size_t nsz; - - /* handle initial alloc */ - if (*s == NULL){ - *s=estrdup(src); - *sz=strlen(src)+1; - return; - } + struct berval **bv, **p; + char op, *var, *val; + + if (entry == NULL) + return; + + bv = ldap_get_values_len(ld, entry, "sudoOption"); + if (bv == NULL) + return; + + /* walk through options */ + for (p = bv; *p != NULL; p++) { + var = estrdup((*p)->bv_val); + DPRINTF(("ldap sudoOption: '%s'", var), 2); + + /* check for equals sign past first char */ + val = strchr(var, '='); + if (val > var) { + *val++ = '\0'; /* split on = and truncate var */ + op = *(val - 2); /* peek for += or -= cases */ + if (op == '+' || op == '-') { + *(val - 2) = '\0'; /* found, remove extra char */ + /* case var+=val or var-=val */ + set_default(var, val, (int) op); + } else { + /* case var=val */ + set_default(var, val, TRUE); + } + } else if (*var == '!') { + /* case !var Boolean False */ + set_default(var + 1, NULL, FALSE); + } else { + /* case var Boolean True */ + set_default(var, NULL, TRUE); + } + efree(var); + } - /* handle realloc */ - nsz= strlen(*s) + strlen(src) + 1; - if (*sz < nsz) *s=erealloc( (void *)*s , *sz=nsz*2); - strlcat(*s,src,*sz); + ldap_value_free_len(bv); } - /* * builds together a filter to check against ldap */ char * -sudo_ldap_build_pass1() +sudo_ldap_build_pass1(pw) + struct passwd *pw; { - struct group *grp; - gid_t *grplist=NULL; - int ngrps; - int i; - - char *b=NULL; - size_t sz; - - /* global OR */ - ncat(&b,&sz,"(|"); - - /* build filter sudoUser=user_name */ - ncat(&b,&sz,"(sudoUser="); - ncat(&b,&sz,user_name); - ncat(&b,&sz,")"); - - /* Append primary group */ - grp=getgrgid(getgid()); - if (grp!=NULL){ - ncat(&b,&sz,"(sudoUser=%"); - ncat(&b,&sz,grp->gr_name); - ncat(&b,&sz,")"); - } - - /* handle arbitrary number of groups */ - if (0<(ngrps=getgroups(0,NULL))){ - grplist=calloc(ngrps,sizeof(gid_t)); - if (grplist!=NULL && (0gr_name); - ncat(&b,&sz,")"); - } - } - } - - - /* Add ALL to list */ - ncat(&b,&sz,"(sudoUser=ALL)"); - - /* End of OR List */ - ncat(&b,&sz,")"); - return b ; + struct group *grp; + size_t sz; + char *buf; + int i; + + /* Start with (|(sudoUser=USERNAME)(sudoUser=ALL)) + NUL */ + sz = 29 + strlen(pw->pw_name); + + /* Add space for groups */ + if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) + sz += 12 + strlen(grp->gr_name); /* primary group */ + for (i = 0; i < user_ngroups; i++) { + if (user_groups[i] == pw->pw_gid) + continue; + if ((grp = sudo_getgrgid(user_groups[i])) != NULL) + sz += 12 + strlen(grp->gr_name); /* supplementary group */ + } + buf = emalloc(sz); + + /* Global OR + sudoUser=user_name filter */ + (void) strlcpy(buf, "(|(sudoUser=", sz); + (void) strlcat(buf, pw->pw_name, sz); + (void) strlcat(buf, ")", sz); + + /* Append primary group */ + if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) { + (void) strlcat(buf, "(sudoUser=%", sz); + (void) strlcat(buf, grp->gr_name, sz); + (void) strlcat(buf, ")", sz); + } + + /* Append supplementary groups */ + for (i = 0; i < user_ngroups; i++) { + if (user_groups[i] == pw->pw_gid) + continue; + if ((grp = sudo_getgrgid(user_groups[i])) != NULL) { + (void) strlcat(buf, "(sudoUser=%", sz); + (void) strlcat(buf, grp->gr_name, sz); + (void) strlcat(buf, ")", sz); + } + } + + /* Add ALL to list and end the global OR */ + if (strlcat(buf, "(sudoUser=ALL))", sz) >= sz) + errorx(1, "sudo_ldap_build_pass1 allocation mismatch"); + + return(buf); } /* - * Map yes/true/on to 1, no/false/off to 0, else -1 + * Map yes/true/on to TRUE, no/false/off to FALSE, else -1 */ int _atobool(s) - char *s; + const char *s; { - if (!strcasecmp(s,"yes") || !strcasecmp(s,"true") || !strcasecmp(s,"on")) - return 1; - if (!strcasecmp(s,"no") || !strcasecmp(s,"false") || !strcasecmp(s,"off")) - return 0; - return -1; + switch (*s) { + case 'y': + case 'Y': + if (strcasecmp(s, "yes") == 0) + return(TRUE); + break; + case 't': + case 'T': + if (strcasecmp(s, "true") == 0) + return(TRUE); + break; + case 'o': + case 'O': + if (strcasecmp(s, "on") == 0) + return(TRUE); + if (strcasecmp(s, "off") == 0) + return(FALSE); + break; + case 'n': + case 'N': + if (strcasecmp(s, "no") == 0) + return(FALSE); + break; + case 'f': + case 'F': + if (strcasecmp(s, "false") == 0) + return(FALSE); + break; + } + return(-1); +} + +static void +sudo_ldap_read_secret(path) + const char *path; +{ + FILE *fp; + char buf[LINE_MAX], *cp; + + if ((fp = fopen(_PATH_LDAP_SECRET, "r")) != NULL) { + if (fgets(buf, sizeof(buf), fp) != NULL) { + if ((cp = strchr(buf, '\n')) != NULL) + *cp = '\0'; + /* copy to bindpw and binddn */ + efree(ldap_conf.bindpw); + ldap_conf.bindpw = estrdup(buf); + efree(ldap_conf.binddn); + ldap_conf.binddn = ldap_conf.rootbinddn; + ldap_conf.rootbinddn = NULL; + } + fclose(fp); + } } int sudo_ldap_read_config() { - FILE *f; - char buf[BUF_SIZ]; - char *c; - char *keyword; - char *value; - - ldap_conf.tls_checkpeer=-1; /* default */ - - f=fopen(_PATH_LDAP_CONF,"r"); - if (!f) return 0; - while (f && fgets(buf,sizeof(buf)-1,f)){ - c=buf; - if (*c == '#') continue; /* ignore comment */ - if (*c == '\n') continue; /* skip newline */ - if (!*c) continue; /* incomplete last line */ - - /* skip whitespace before keyword */ - while (isspace(*c)) c++; - keyword=c; - - /* properly terminate keyword string */ - while (*c && !isspace(*c)) c++; - if (*c) { - *c='\0'; /* terminate keyword */ - c++; - } - - /* skip whitespace before value */ - while (isspace(*c)) c++; - value=c; - - /* trim whitespace after value */ - while (*c) c++; /* wind to end */ - while (--c > value && isspace(*c)) *c='\0'; - - /* The following macros make the code much more readable */ - -#define MATCH_S(x,y) if (!strcasecmp(keyword,x)) \ - { if (y) free(y); y=estrdup(value); } -#define MATCH_I(x,y) if (!strcasecmp(keyword,x)) { y=atoi(value); } -#define MATCH_B(x,y) if (!strcasecmp(keyword,x)) { y=_atobool(value); } - - - - /* parse values using a continues chain of - * if else if else if else if else ... */ - MATCH_S("host", ldap_conf.host) - else MATCH_I("port", ldap_conf.port) - else MATCH_S("ssl", ldap_conf.ssl) - else MATCH_B("tls_checkpeer", ldap_conf.tls_checkpeer) - else MATCH_S("tls_cacertfile", ldap_conf.tls_cacertfile) - else MATCH_S("tls_cacertdir", ldap_conf.tls_cacertdir) - else MATCH_S("tls_randfile", ldap_conf.tls_random_file) - else MATCH_S("tls_ciphers", ldap_conf.tls_cipher_suite) - else MATCH_S("tls_cert", ldap_conf.tls_certfile) - else MATCH_S("tls_key", ldap_conf.tls_keyfile) - else MATCH_I("ldap_version", ldap_conf.version) - else MATCH_S("uri", ldap_conf.uri) - else MATCH_S("binddn", ldap_conf.binddn) - else MATCH_S("bindpw", ldap_conf.bindpw) - else MATCH_S("sudoers_base", ldap_conf.base) - else MATCH_I("sudoers_debug", ldap_conf.debug) - else { - - /* The keyword was unrecognized. Since this config file is shared - * by multiple programs, it is appropriate to silently ignore options this - * program does not understand + FILE *fp; + char *cp, *keyword, *value; + struct ldap_config_table *cur; + + /* defaults */ + ldap_conf.version = 3; + ldap_conf.port = -1; + ldap_conf.tls_checkpeer = -1; + ldap_conf.timelimit = -1; + ldap_conf.bind_timelimit = -1; + ldap_conf.use_sasl = -1; + ldap_conf.rootuse_sasl = -1; + + if ((fp = fopen(_PATH_LDAP_CONF, "r")) == NULL) + return(FALSE); + + while ((cp = sudo_parseln(fp)) != NULL) { + if (*cp == '\0') + continue; /* skip empty line */ + + /* split into keyword and value */ + keyword = cp; + while (*cp && !isblank((unsigned char) *cp)) + cp++; + if (*cp) + *cp++ = '\0'; /* terminate keyword */ + + /* skip whitespace before value */ + while (isblank((unsigned char) *cp)) + cp++; + value = cp; + + /* Look up keyword in config table. */ + for (cur = ldap_conf_table; cur->conf_str != NULL; cur++) { + if (strcasecmp(keyword, cur->conf_str) == 0) { + switch (cur->type) { + case CONF_BOOL: + *(int *)(cur->valp) = _atobool(value); + break; + case CONF_INT: + *(int *)(cur->valp) = atoi(value); + break; + case CONF_STR: + efree(*(char **)(cur->valp)); + *(char **)(cur->valp) = estrdup(value); + break; + } + break; + } + } + } + fclose(fp); + + if (!ldap_conf.host) + ldap_conf.host = estrdup("localhost"); + + if (ldap_conf.bind_timelimit > 0) + ldap_conf.bind_timelimit *= 1000; /* convert to ms */ + + if (ldap_conf.debug > 1) { + fprintf(stderr, "LDAP Config Summary\n"); + fprintf(stderr, "===================\n"); + if (ldap_conf.uri) { + fprintf(stderr, "uri %s\n", ldap_conf.uri); + } else { + fprintf(stderr, "host %s\n", ldap_conf.host ? + ldap_conf.host : "(NONE)"); + fprintf(stderr, "port %d\n", ldap_conf.port); + } + fprintf(stderr, "ldap_version %d\n", ldap_conf.version); + + fprintf(stderr, "sudoers_base %s\n", ldap_conf.base ? + ldap_conf.base : "(NONE) <---Sudo will ignore ldap)"); + fprintf(stderr, "binddn %s\n", ldap_conf.binddn ? + ldap_conf.binddn : "(anonymous)"); + fprintf(stderr, "bindpw %s\n", ldap_conf.bindpw ? + ldap_conf.bindpw : "(anonymous)"); + if (ldap_conf.bind_timelimit > 0) + fprintf(stderr, "bind_timelimit %d\n", ldap_conf.bind_timelimit); + if (ldap_conf.timelimit > 0) + fprintf(stderr, "timelimit %d\n", ldap_conf.timelimit); + fprintf(stderr, "ssl %s\n", ldap_conf.ssl ? + ldap_conf.ssl : "(no)"); + if (ldap_conf.tls_checkpeer != -1) + fprintf(stderr, "tls_checkpeer %s\n", ldap_conf.tls_checkpeer ? + "(yes)" : "(no)"); + if (ldap_conf.tls_cacertfile != NULL) + fprintf(stderr, "tls_cacertfile %s\n", ldap_conf.tls_cacertfile); + if (ldap_conf.tls_cacertdir != NULL) + fprintf(stderr, "tls_cacertdir %s\n", ldap_conf.tls_cacertdir); + if (ldap_conf.tls_random_file != NULL) + fprintf(stderr, "tls_random_file %s\n", ldap_conf.tls_random_file); + if (ldap_conf.tls_cipher_suite != NULL) + fprintf(stderr, "tls_cipher_suite %s\n", ldap_conf.tls_cipher_suite); + if (ldap_conf.tls_certfile != NULL) + fprintf(stderr, "tls_certfile %s\n", ldap_conf.tls_certfile); + if (ldap_conf.tls_keyfile != NULL) + fprintf(stderr, "tls_keyfile %s\n", ldap_conf.tls_keyfile); +#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S + if (ldap_conf.use_sasl != -1) { + fprintf(stderr, "use_sasl %s\n", + ldap_conf.use_sasl ? "yes" : "no"); + fprintf(stderr, "sasl_auth_id %s\n", ldap_conf.sasl_auth_id ? + ldap_conf.sasl_auth_id : "(NONE)"); + fprintf(stderr, "rootuse_sasl %d\n", ldap_conf.rootuse_sasl); + fprintf(stderr, "rootsasl_auth_id %s\n", ldap_conf.rootsasl_auth_id ? + ldap_conf.rootsasl_auth_id : "(NONE)"); + fprintf(stderr, "sasl_secprops %s\n", ldap_conf.sasl_secprops ? + ldap_conf.sasl_secprops : "(NONE)"); + fprintf(stderr, "krb5_ccname %s\n", ldap_conf.krb5_ccname ? + ldap_conf.krb5_ccname : "(NONE)"); + } +#endif + fprintf(stderr, "===================\n"); + } + if (!ldap_conf.base) + return(FALSE); /* if no base is defined, ignore LDAP */ + + /* + * Interpret SSL option */ + if (ldap_conf.ssl != NULL) { + if (strcasecmp(ldap_conf.ssl, "start_tls") == 0) + ldap_conf.ssl_mode = SUDO_LDAP_STARTTLS; + else if (_atobool(ldap_conf.ssl)) + ldap_conf.ssl_mode = SUDO_LDAP_SSL; } - } /* parse next line */ +#if defined(HAVE_LDAPSSL_SET_STRENGTH) && !defined(LDAP_OPT_X_TLS_REQUIRE_CERT) + if (ldap_conf.tls_checkpeer != -1) { + ldapssl_set_strength(NULL, + ldap_conf.tls_checkpeer ? LDAPSSL_AUTH_CERT : LDAPSSL_AUTH_WEAK); + } +#endif - if (f) fclose(f); +#ifndef HAVE_LDAP_INITIALIZE + /* Convert uri list to host list if no ldap_initialize(). */ + if (ldap_conf.uri) { + if (sudo_ldap_parse_uri(ldap_conf.uri) != 0) + return(FALSE); + free(ldap_conf.uri); + ldap_conf.uri = NULL; + ldap_conf.port = LDAP_PORT; + } +#endif - /* defaults */ - if (!ldap_conf.version) ldap_conf.version=3; - if (!ldap_conf.port) ldap_conf.port=389; - if (!ldap_conf.host) ldap_conf.host=estrdup("localhost"); + if (!ldap_conf.uri) { + /* Use port 389 for plaintext LDAP and port 636 for SSL LDAP */ + if (ldap_conf.port < 0) + ldap_conf.port = + ldap_conf.ssl_mode == SUDO_LDAP_SSL ? LDAPS_PORT : LDAP_PORT; + +#ifdef HAVE_LDAP_CREATE + /* + * Cannot specify port directly to ldap_create(), each host must + * include :port to override the default. + */ + if (ldap_conf.port != LDAP_PORT) + sudo_ldap_conf_add_ports(); +#endif + } + /* If rootbinddn set, read in /etc/ldap.secret if it exists. */ + if (ldap_conf.rootbinddn) + sudo_ldap_read_secret(_PATH_LDAP_SECRET); - if (ldap_conf.debug>1) { - printf("LDAP Config Summary\n"); - printf("===================\n"); -#ifdef HAVE_LDAP_INITIALIZE - if (ldap_conf.uri){ - printf("uri %s\n", ldap_conf.uri); - } else +#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S + /* + * Make sure we can open the file specified by krb5_ccname. + */ + if (ldap_conf.krb5_ccname != NULL) { + if (strncasecmp(ldap_conf.krb5_ccname, "FILE:", 5) == 0 || + strncasecmp(ldap_conf.krb5_ccname, "WRFILE:", 7) == 0) { + value = ldap_conf.krb5_ccname + + (ldap_conf.krb5_ccname[4] == ':' ? 5 : 7); + if ((fp = fopen(value, "r")) != NULL) { + DPRINTF(("using krb5 credential cache: %s", value), 1); + fclose(fp); + } else { + /* Can't open it, just ignore the entry. */ + DPRINTF(("unable to open krb5 credential cache: %s", value), 1); + efree(ldap_conf.krb5_ccname); + ldap_conf.krb5_ccname = NULL; + } + } + } #endif - { - printf("host %s\n", ldap_conf.host ? - ldap_conf.host : "(NONE)"); - printf("port %d\n", ldap_conf.port); - } - printf("ldap_version %d\n", ldap_conf.version); - - printf("sudoers_base %s\n", ldap_conf.base ? - ldap_conf.base : "(NONE) <---Sudo will ignore ldap)"); - printf("binddn %s\n", ldap_conf.binddn ? - ldap_conf.binddn : "(anonymous)"); - printf("bindpw %s\n", ldap_conf.bindpw ? - ldap_conf.bindpw : "(anonymous)"); -#ifdef HAVE_LDAP_START_TLS_S - printf("ssl %s\n", ldap_conf.ssl ? - ldap_conf.ssl : "(no)"); -#endif - printf("===================\n"); - } - - /* if no base is defined, ignore LDAP */ - if (!ldap_conf.base) return 0; - /* All is good */ - return 1; + return(TRUE); } /* - like perl's join(sep,@ARGS) -*/ -char * -_ldap_join_values(sep,v) - char *sep; - char **v; + * Extract the dn from an entry and return the first rdn from it. + */ +static char * +sudo_ldap_get_first_rdn(ld, entry) + LDAP *ld; + LDAPMessage *entry; { - char **p=NULL; - char *b=NULL; - size_t sz=0; - - /* paste values together */ - for (p=v; p && *p;p++){ - if (p!=v && sep!=NULL) ncat(&b,&sz,sep); /* append seperator */ - ncat(&b,&sz,*p); /* append value */ - } - - /* sanity check */ - if (b[0]=='\0'){ - /* something went wrong, put something here */ - ncat(&b,&sz,"(empty list)"); /* append value */ - } - - /* all done */ - return b; +#ifdef HAVE_LDAP_STR2DN + char *dn, *rdn = NULL; + LDAPDN tmpDN; + + if ((dn = ldap_get_dn(ld, entry)) == NULL) + return(NULL); + if (ldap_str2dn(dn, &tmpDN, LDAP_DN_FORMAT_LDAP) == LDAP_SUCCESS) { + ldap_rdn2str(tmpDN[0], &rdn, LDAP_DN_FORMAT_UFN); + ldap_dnfree(tmpDN); + } + ldap_memfree(dn); + return(rdn); +#else + char *dn, **edn; + + if ((dn = ldap_get_dn(ld, entry)) == NULL) + return(NULL); + edn = ldap_explode_dn(dn, 1); + ldap_memfree(dn); + return(edn ? edn[0] : NULL); +#endif } -char * sudo_ldap_cm_list=NULL; -size_t sudo_ldap_cm_list_size; +/* + * Fetch and display the global Options. + */ +int +sudo_ldap_display_defaults(nss, pw, lbuf) + struct sudo_nss *nss; + struct passwd *pw; + struct lbuf *lbuf; +{ + struct berval **bv, **p; + LDAP *ld = (LDAP *) nss->handle; + LDAPMessage *entry = NULL, *result = NULL; + char *prefix = NULL; + int rc, count = 0; + + if (ld == NULL) + return(-1); + + rc = ldap_search_ext_s(ld, ldap_conf.base, LDAP_SCOPE_SUBTREE, + "cn=defaults", NULL, 0, NULL, NULL, NULL, 0, &result); + if (rc == LDAP_SUCCESS && (entry = ldap_first_entry(ld, result))) { + bv = ldap_get_values_len(ld, entry, "sudoOption"); + if (bv != NULL) { + if (lbuf->len == 0) + prefix = " "; + else + prefix = ", "; + for (p = bv; *p != NULL; p++) { + lbuf_append(lbuf, prefix, (*p)->bv_val, NULL); + prefix = ", "; + count++; + } + ldap_value_free_len(bv); + } + } + if (result) + ldap_msgfree(result); + return(count); +} -#define SAVE_LIST(x) ncat(&sudo_ldap_cm_list,&sudo_ldap_cm_list_size,(x)) /* - * Walks through search result and returns true if we have a - * command match + * STUB */ int -sudo_ldap_add_match(ld,entry) - LDAP *ld; - LDAPMessage *entry; +sudo_ldap_display_bound_defaults(nss, pw, lbuf) + struct sudo_nss *nss; + struct passwd *pw; + struct lbuf *lbuf; { - char **v=NULL; - char *dn; - char **edn; - - /* if we are not collecting matches, then don't print them */ - if (printmatches != TRUE) return 1; - - /* collect the dn, only show the rdn */ - dn=ldap_get_dn(ld,entry); - edn=dn ? ldap_explode_dn(dn,1) : NULL; - SAVE_LIST("\nLDAP Role: "); - SAVE_LIST((edn && *edn) ? *edn : "UNKNOWN"); - SAVE_LIST("\n"); - if (dn) ldap_memfree(dn); - if (edn) ldap_value_free(edn); - - /* get the Runas Values from the entry */ - v=ldap_get_values(ld,entry,"sudoRunAs"); - if (v && *v){ - SAVE_LIST(" RunAs: ("); - SAVE_LIST(_ldap_join_values(", ",v)); - SAVE_LIST(")\n"); - } - if (v) ldap_value_free(v); - - /* get the Command Values from the entry */ - v=ldap_get_values(ld,entry,"sudoCommand"); - if (v && *v){ - SAVE_LIST(" Commands:\n "); - SAVE_LIST(_ldap_join_values("\n ",v)); - SAVE_LIST("\n"); - } else { - SAVE_LIST(" Commands: NONE\n"); - } - if (v) ldap_value_free(v); - - return 0; /* Don't stop at the first match */ + return(1); } -#undef SAVE_LIST -void -sudo_ldap_list_matches() +/* + * Print a record in the short form, ala file sudoers. + */ +int +sudo_ldap_display_entry_short(ld, entry, lbuf) + LDAP *ld; + LDAPMessage *entry; + struct lbuf *lbuf; { - if (sudo_ldap_cm_list!=NULL) printf("%s",sudo_ldap_cm_list); + struct berval **bv, **p; + int count = 0; + + lbuf_append(lbuf, " (", NULL); + + /* get the RunAsUser Values from the entry */ + bv = ldap_get_values_len(ld, entry, "sudoRunAsUser"); + if (bv == NULL) + bv = ldap_get_values_len(ld, entry, "sudoRunAs"); + if (bv != NULL) { + for (p = bv; *p != NULL; p++) { + if (p != bv) + lbuf_append(lbuf, ", ", NULL); + lbuf_append(lbuf, (*p)->bv_val, NULL); + } + ldap_value_free_len(bv); + } else + lbuf_append(lbuf, def_runas_default, NULL); + + /* get the RunAsGroup Values from the entry */ + bv = ldap_get_values_len(ld, entry, "sudoRunAsGroup"); + if (bv != NULL) { + lbuf_append(lbuf, " : ", NULL); + for (p = bv; *p != NULL; p++) { + if (p != bv) + lbuf_append(lbuf, ", ", NULL); + lbuf_append(lbuf, (*p)->bv_val, NULL); + } + ldap_value_free_len(bv); + } + lbuf_append(lbuf, ") ", NULL); + + /* get the Option Values from the entry */ + bv = ldap_get_values_len(ld, entry, "sudoOption"); + if (bv != NULL) { + char *cp, *tag; + + for (p = bv; *p != NULL; p++) { + cp = (*p)->bv_val; + if (*cp == '!') + cp++; + tag = NULL; + if (strcmp(cp, "authenticate") == 0) + tag = (*p)->bv_val[0] == '!' ? + "NOPASSWD: " : "PASSWD: "; + else if (strcmp(cp, "noexec") == 0) + tag = (*p)->bv_val[0] == '!' ? + "EXEC: " : "NOEXEC: "; + else if (strcmp(cp, "setenv") == 0) + tag = (*p)->bv_val[0] == '!' ? + "NOSETENV: " : "SETENV: "; + if (tag != NULL) + lbuf_append(lbuf, tag, NULL); + /* XXX - ignores other options */ + } + ldap_value_free_len(bv); + } + + /* get the Command Values from the entry */ + bv = ldap_get_values_len(ld, entry, "sudoCommand"); + if (bv != NULL) { + for (p = bv; *p != NULL; p++) { + if (p != bv) + lbuf_append(lbuf, ", ", NULL); + lbuf_append(lbuf, (*p)->bv_val, NULL); + count++; + } + ldap_value_free_len(bv); + } + + lbuf_print(lbuf); /* forces a newline */ + return(count); } /* - * like sudoers_lookup() - only LDAP style - * + * Print a record in the long form. */ +int +sudo_ldap_display_entry_long(ld, entry, lbuf) + LDAP *ld; + LDAPMessage *entry; + struct lbuf *lbuf; +{ + struct berval **bv, **p; + char *rdn; + int count = 0; + + /* extract the dn, only show the first rdn */ + rdn = sudo_ldap_get_first_rdn(ld, entry); + lbuf_print(lbuf); /* force a newline */ + lbuf_append(lbuf, "LDAP Role: ", rdn ? rdn : "UNKNOWN", NULL); + lbuf_print(lbuf); + if (rdn) + ldap_memfree(rdn); + + /* get the RunAsUser Values from the entry */ + lbuf_append(lbuf, " RunAsUsers: ", NULL); + bv = ldap_get_values_len(ld, entry, "sudoRunAsUser"); + if (bv == NULL) + bv = ldap_get_values_len(ld, entry, "sudoRunAs"); + if (bv != NULL) { + for (p = bv; *p != NULL; p++) { + if (p != bv) + lbuf_append(lbuf, ", ", NULL); + lbuf_append(lbuf, (*p)->bv_val, NULL); + } + ldap_value_free_len(bv); + } else + lbuf_append(lbuf, def_runas_default, NULL); + lbuf_print(lbuf); + + /* get the RunAsGroup Values from the entry */ + bv = ldap_get_values_len(ld, entry, "sudoRunAsGroup"); + if (bv != NULL) { + lbuf_append(lbuf, " RunAsGroups: ", NULL); + for (p = bv; *p != NULL; p++) { + if (p != bv) + lbuf_append(lbuf, ", ", NULL); + lbuf_append(lbuf, (*p)->bv_val, NULL); + } + ldap_value_free_len(bv); + lbuf_print(lbuf); + } + + /* get the Option Values from the entry */ + bv = ldap_get_values_len(ld, entry, "sudoOption"); + if (bv != NULL) { + lbuf_append(lbuf, " Options: ", NULL); + for (p = bv; *p != NULL; p++) { + if (p != bv) + lbuf_append(lbuf, ", ", NULL); + lbuf_append(lbuf, (*p)->bv_val, NULL); + } + ldap_value_free_len(bv); + lbuf_print(lbuf); + } + + /* get the Command Values from the entry */ + bv = ldap_get_values_len(ld, entry, "sudoCommand"); + if (bv != NULL) { + lbuf_append(lbuf, " Commands:", NULL); + lbuf_print(lbuf); + for (p = bv; *p != NULL; p++) { + lbuf_append(lbuf, "\t", (*p)->bv_val, NULL); + lbuf_print(lbuf); + count++; + } + ldap_value_free_len(bv); + } + return(count); +} + +/* + * Like sudo_ldap_lookup(), except we just print entries. + */ int -sudo_ldap_check(pwflag) -int pwflag; +sudo_ldap_display_privs(nss, pw, lbuf) + struct sudo_nss *nss; + struct passwd *pw; + struct lbuf *lbuf; { + LDAP *ld = (LDAP *) nss->handle; + LDAPMessage *entry = NULL, *result = NULL; + char *filt; + int rc, do_netgr, count = 0; - LDAP *ld=NULL; - - /* Used for searches */ - LDAPMessage *result=NULL; - LDAPMessage *entry=NULL; - /* used to parse attributes */ - char *filt; - /* temp/final return values */ - int rc=0; - int ret=0; - int pass=0; - /* flags */ - int ldap_user_matches=0; - int ldap_host_matches=0; - - if (!sudo_ldap_read_config()) return VALIDATE_ERROR; - - /* macro to set option, error on failure plus consistent debugging */ -#define SET_OPT(opt,optname,val) \ - if (ldap_conf.val!=NULL) { \ - if (ldap_conf.debug>1) fprintf(stderr, \ - "ldap_set_option(LDAP_OPT_%s,\"%s\")\n",optname,ldap_conf.val); \ - rc=ldap_set_option(ld,opt,ldap_conf.val); \ - if(rc != LDAP_OPT_SUCCESS){ \ - fprintf(stderr,"ldap_set_option(LDAP_OPT_%s,\"%s\")=%d: %s\n", \ - optname, ldap_conf.val, rc, ldap_err2string(rc)); \ - return VALIDATE_ERROR ; \ - } \ - } \ - - /* like above, but assumes val is in int */ -#define SET_OPTI(opt,optname,val) \ - if (ldap_conf.debug>1) fprintf(stderr, \ - "ldap_set_option(LDAP_OPT_%s,0x%02x)\n",optname,ldap_conf.val); \ - rc=ldap_set_option(ld,opt,&ldap_conf.val); \ - if(rc != LDAP_OPT_SUCCESS){ \ - fprintf(stderr,"ldap_set_option(LDAP_OPT_%s,0x%02x)=%d: %s\n", \ - optname, ldap_conf.val, rc, ldap_err2string(rc)); \ - return VALIDATE_ERROR ; \ - } \ - - /* attempt to setup ssl options */ -#ifdef LDAP_OPT_X_TLS_CACERTFILE - SET_OPT(LDAP_OPT_X_TLS_CACERTFILE, "X_TLS_CACERTFILE", tls_cacertfile); -#endif /* LDAP_OPT_X_TLS_CACERTFILE */ - -#ifdef LDAP_OPT_X_TLS_CACERTDIR - SET_OPT(LDAP_OPT_X_TLS_CACERTDIR, "X_TLS_CACERTDIR", tls_cacertdir); -#endif /* LDAP_OPT_X_TLS_CACERTDIR */ - -#ifdef LDAP_OPT_X_TLS_CERTFILE - SET_OPT(LDAP_OPT_X_TLS_CERTFILE, "X_TLS_CERTFILE", tls_certfile); -#endif /* LDAP_OPT_X_TLS_CERTFILE */ - -#ifdef LDAP_OPT_X_TLS_KEYFILE - SET_OPT(LDAP_OPT_X_TLS_KEYFILE, "X_TLS_KEYFILE", tls_keyfile); -#endif /* LDAP_OPT_X_TLS_KEYFILE */ - -#ifdef LDAP_OPT_X_TLS_CIPHER_SUITE - SET_OPT(LDAP_OPT_X_TLS_CIPHER_SUITE, "X_TLS_CIPHER_SUITE", tls_cipher_suite); -#endif /* LDAP_OPT_X_TLS_CIPHER_SUITE */ - -#ifdef LDAP_OPT_X_TLS_RANDOM_FILE - SET_OPT(LDAP_OPT_X_TLS_RANDOM_FILE, "X_TLS_RANDOM_FILE", tls_random_file); -#endif /* LDAP_OPT_X_TLS_RANDOM_FILE */ - -#ifdef LDAP_OPT_X_TLS_REQUIRE_CERT - /* check the server certificate? */ - if (ldap_conf.tls_checkpeer!=-1){ - SET_OPTI(LDAP_OPT_X_TLS_REQUIRE_CERT,"X_TLS_REQUIRE_CERT",tls_checkpeer); - } -#endif /* LDAP_OPT_X_TLS_REQUIRE_CERT */ - - /* attempt connect */ -#ifdef HAVE_LDAP_INITIALIZE - if (ldap_conf.uri) { + if (ld == NULL) + return(-1); - if (ldap_conf.debug>1) fprintf(stderr, - "ldap_initialize(ld,%s)\n",ldap_conf.uri); + /* + * Okay - time to search for anything that matches this user + * Lets limit it to only two queries of the LDAP server + * + * The first pass will look by the username, groups, and + * the keyword ALL. We will then inspect the results that + * came back from the query. We don't need to inspect the + * sudoUser in this pass since the LDAP server already scanned + * it for us. + * + * The second pass will return all the entries that contain + * user netgroups. Then we take the netgroups returned and + * try to match them against the username. + */ + for (do_netgr = 0; do_netgr < 2; do_netgr++) { + filt = do_netgr ? estrdup("sudoUser=+*") : sudo_ldap_build_pass1(pw); + DPRINTF(("ldap search '%s'", filt), 1); + rc = ldap_search_ext_s(ld, ldap_conf.base, LDAP_SCOPE_SUBTREE, filt, + NULL, 0, NULL, NULL, NULL, 0, &result); + efree(filt); + if (rc != LDAP_SUCCESS) + continue; /* no entries for this pass */ + + /* print each matching entry */ + LDAP_FOREACH(entry, ld, result) { + if ((!do_netgr || + sudo_ldap_check_user_netgroup(ld, entry, pw->pw_name)) && + sudo_ldap_check_host(ld, entry)) { + + if (long_list) + count += sudo_ldap_display_entry_long(ld, entry, lbuf); + else + count += sudo_ldap_display_entry_short(ld, entry, lbuf); + } + } + ldap_msgfree(result); + result = NULL; + } + return(count); +} - rc=ldap_initialize(&ld,ldap_conf.uri); - if(rc){ - fprintf(stderr, "ldap_initialize()=%d : %s\n", - rc,ldap_err2string(rc)); - return VALIDATE_ERROR; +int +sudo_ldap_display_cmnd(nss, pw) + struct sudo_nss *nss; + struct passwd *pw; +{ + LDAP *ld = (LDAP *) nss->handle; + LDAPMessage *entry = NULL, *result = NULL; /* used for searches */ + char *filt; /* used to parse attributes */ + int rc, found, do_netgr; /* temp/final return values */ + + if (ld == NULL) + return(1); + + /* + * Okay - time to search for anything that matches this user + * Lets limit it to only two queries of the LDAP server + * + * The first pass will look by the username, groups, and + * the keyword ALL. We will then inspect the results that + * came back from the query. We don't need to inspect the + * sudoUser in this pass since the LDAP server already scanned + * it for us. + * + * The second pass will return all the entries that contain + * user netgroups. Then we take the netgroups returned and + * try to match them against the username. + */ + for (found = FALSE, do_netgr = 0; !found && do_netgr < 2; do_netgr++) { + filt = do_netgr ? estrdup("sudoUser=+*") : sudo_ldap_build_pass1(pw); + DPRINTF(("ldap search '%s'", filt), 1); + rc = ldap_search_ext_s(ld, ldap_conf.base, LDAP_SCOPE_SUBTREE, filt, + NULL, 0, NULL, NULL, NULL, 0, &result); + efree(filt); + if (rc != LDAP_SUCCESS) + continue; /* no entries for this pass */ + + LDAP_FOREACH(entry, ld, result) { + if ((!do_netgr || + sudo_ldap_check_user_netgroup(ld, entry, pw->pw_name)) && + sudo_ldap_check_host(ld, entry) && + sudo_ldap_check_command(ld, entry, NULL) && + sudo_ldap_check_runas(ld, entry)) { + + found = TRUE; + break; + } + } + ldap_msgfree(result); + result = NULL; } - } else -#endif /* HAVE_LDAP_INITIALIZE */ - if (ldap_conf.host) { - if (ldap_conf.debug>1) fprintf(stderr, - "ldap_init(%s,%d)\n",ldap_conf.host,ldap_conf.port); + if (found) + printf("%s%s%s\n", safe_cmnd ? safe_cmnd : user_cmnd, + user_args ? " " : "", user_args ? user_args : ""); + return(!found); +} - ld=ldap_init(ldap_conf.host,ldap_conf.port); - if (!ld) { - fprintf(stderr, "ldap_init(): errno=%d : %s\n", - errno, strerror(errno)); - return VALIDATE_ERROR; +#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S +static int +sudo_ldap_sasl_interact(ld, flags, _auth_id, _interact) + LDAP *ld; + unsigned int flags; + void *_auth_id; + void *_interact; +{ + char *auth_id = (char *)_auth_id; + sasl_interact_t *interact = (sasl_interact_t *)_interact; + + for (; interact->id != SASL_CB_LIST_END; interact++) { + if (interact->id != SASL_CB_USER) + return(LDAP_PARAM_ERROR); + + if (auth_id != NULL) + interact->result = auth_id; + else if (interact->defresult != NULL) + interact->result = interact->defresult; + else + interact->result = ""; + + interact->len = strlen(interact->result); +#if SASL_VERSION_MAJOR < 2 + interact->result = estrdup(interact->result); +#endif /* SASL_VERSION_MAJOR < 2 */ } - } + return(LDAP_SUCCESS); +} +#endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */ -#ifdef LDAP_OPT_PROTOCOL_VERSION +/* + * Set LDAP options based on the config table. + */ +int +sudo_ldap_set_options(ld) + LDAP *ld; +{ + struct ldap_config_table *cur; + int rc; - /* Set the LDAP Protocol version */ - SET_OPTI(LDAP_OPT_PROTOCOL_VERSION,"PROTOCOL_VERSION", version); + /* Set ber options */ +#ifdef LBER_OPT_DEBUG_LEVEL + if (ldap_conf.ldap_debug) + ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &ldap_conf.ldap_debug); +#endif -#endif /* LDAP_OPT_PROTOCOL_VERSION */ + /* Set simple LDAP options */ + for (cur = ldap_conf_table; cur->conf_str != NULL; cur++) { + LDAP *conn; + int ival; + char *sval; + + if (cur->opt_val == -1) + continue; + + conn = cur->connected ? ld : NULL; + switch (cur->type) { + case CONF_BOOL: + case CONF_INT: + ival = *(int *)(cur->valp); + if (ival >= 0) { + rc = ldap_set_option(conn, cur->opt_val, &ival); + if (rc != LDAP_OPT_SUCCESS) { + warningx("ldap_set_option: %s -> %d: %s", + cur->conf_str, ival, ldap_err2string(rc)); + return(-1); + } + DPRINTF(("ldap_set_option: %s -> %d", cur->conf_str, ival), 1); + } + break; + case CONF_STR: + sval = *(char **)(cur->valp); + if (sval != NULL) { + rc = ldap_set_option(conn, cur->opt_val, sval); + if (rc != LDAP_OPT_SUCCESS) { + warningx("ldap_set_option: %s -> %s: %s", + cur->conf_str, sval, ldap_err2string(rc)); + return(-1); + } + DPRINTF(("ldap_set_option: %s -> %s", cur->conf_str, sval), 1); + } + break; + } + } -#ifdef HAVE_LDAP_START_TLS_S - /* Turn on TLS */ - if (ldap_conf.ssl && !strcasecmp(ldap_conf.ssl, "start_tls")){ - rc = ldap_start_tls_s(ld, NULL, NULL); - if (rc != LDAP_SUCCESS) { - fprintf(stderr, "ldap_start_tls_s(): %d: %s\n", rc, ldap_err2string(rc)); - ldap_unbind(ld); - return VALIDATE_ERROR; - } - - if (ldap_conf.debug) printf("ldap_start_tls_s() ok\n"); - } -#endif /* HAVE_LDAP_START_TLS_S */ - - /* Actually connect */ - - rc=ldap_simple_bind_s(ld,ldap_conf.binddn,ldap_conf.bindpw); - if(rc){ - fprintf(stderr,"ldap_simple_bind_s()=%d : %s\n", - rc, ldap_err2string(rc)); - return VALIDATE_ERROR ; - } - - if (ldap_conf.debug) printf("ldap_bind() ok\n"); - - - /* Parse Default Options */ - - rc=ldap_search_s(ld,ldap_conf.base,LDAP_SCOPE_ONELEVEL, - "cn=defaults",NULL,0,&result); - if (!rc && (entry=ldap_first_entry(ld,result))){ - if (ldap_conf.debug) printf("found:%s\n",ldap_get_dn(ld,entry)); - sudo_ldap_parse_options(ld,entry); - } else { - if (ldap_conf.debug) printf("no default options found!\n"); - } - - if (result) ldap_msgfree(result); - result=NULL; - - /* - * Okay - time to search for anything that matches this user - * Lets limit it to only two queries of the LDAP server - * - * The first pass will look by the username, groups, and - * the keyword ALL. We will then inspect the results that - * came back from the query. We don't need to inspect the - * sudoUser in this pass since the LDAP server already scanned - * it for us. - * - * The second pass will return all the entries that contain - * user netgroups. Then we take the netgroups returned and - * try to match them against the username. - * - */ - - for(pass=1;!ret && pass<=2;pass++){ - - if (pass==1) { - /* Want the entries that match our usernames or groups */ - filt=sudo_ldap_build_pass1(); - } else { /* pass=2 */ - /* Want the entries that have user netgroups in them. */ - filt=strdup("sudoUser=+*"); - } - if (ldap_conf.debug) printf("ldap search '%s'\n",filt); - rc=ldap_search_s(ld,ldap_conf.base,LDAP_SCOPE_ONELEVEL, - filt,NULL,0,&result); - if (rc) { - if (ldap_conf.debug) printf("nothing found for '%s'\n",filt); - } - if (filt) free (filt); - /* parse each entry returned from this most recent search */ - for( - entry=rc ? NULL : ldap_first_entry(ld,result); - entry!=NULL; - entry=ldap_next_entry(ld,entry)) +#ifdef LDAP_OPT_NETWORK_TIMEOUT + /* Convert bind_timelimit to a timeval */ + if (ldap_conf.bind_timelimit > 0) { + struct timeval tv; + tv.tv_sec = ldap_conf.bind_timelimit / 1000; + tv.tv_usec = 0; + rc = ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &tv); + if (rc != LDAP_OPT_SUCCESS) { + warningx("ldap_set_option(NETWORK_TIMEOUT, %ld): %s", + (long)tv.tv_sec, ldap_err2string(rc)); + return(-1); + } + DPRINTF(("ldap_set_option(LDAP_OPT_NETWORK_TIMEOUT, %ld)\n", + (long)tv.tv_sec), 1); + } +#endif + +#if defined(LDAP_OPT_X_TLS) && !defined(HAVE_LDAPSSL_INIT) + if (ldap_conf.ssl_mode == SUDO_LDAP_SSL) { + int val = LDAP_OPT_X_TLS_HARD; + rc = ldap_set_option(ld, LDAP_OPT_X_TLS, &val); + if (rc != LDAP_SUCCESS) { + warningx("ldap_set_option(LDAP_OPT_X_TLS, LDAP_OPT_X_TLS_HARD): %s", + ldap_err2string(rc)); + return(-1); + } + DPRINTF(("ldap_set_option(LDAP_OPT_X_TLS, LDAP_OPT_X_TLS_HARD)\n"), 1); + } +#endif + return(0); +} + +/* + * Connect to the LDAP server specified by ld + */ +static int +sudo_ldap_bind_s(ld) + LDAP *ld; +{ + int rc; + const char *old_ccname = user_ccname; +#ifdef HAVE_GSS_KRB5_CCACHE_NAME + unsigned int status; +#endif + +#ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S + if (ldap_conf.rootuse_sasl == TRUE || + (ldap_conf.rootuse_sasl != FALSE && ldap_conf.use_sasl == TRUE)) { + void *auth_id = ldap_conf.rootsasl_auth_id ? + ldap_conf.rootsasl_auth_id : ldap_conf.sasl_auth_id; + + if (ldap_conf.krb5_ccname != NULL) { +#ifdef HAVE_GSS_KRB5_CCACHE_NAME + if (gss_krb5_ccache_name(&status, ldap_conf.krb5_ccname, &old_ccname) + != GSS_S_COMPLETE) { + old_ccname = NULL; + DPRINTF(("gss_krb5_ccache_name() failed: %d", status), 1); + } +#else + setenv("KRB5CCNAME", ldap_conf.krb5_ccname, TRUE); +#endif + } + rc = ldap_sasl_interactive_bind_s(ld, ldap_conf.binddn, "GSSAPI", + NULL, NULL, LDAP_SASL_QUIET, sudo_ldap_sasl_interact, auth_id); + if (ldap_conf.krb5_ccname != NULL) { +#ifdef HAVE_GSS_KRB5_CCACHE_NAME + if (gss_krb5_ccache_name(&status, old_ccname, NULL) != GSS_S_COMPLETE) + DPRINTF(("gss_krb5_ccache_name() failed: %d", status), 1); +#else + if (old_ccname != NULL) + setenv("KRB5CCNAME", old_ccname, TRUE); + else + unsetenv("KRB5CCNAME"); +#endif + } + if (rc != LDAP_SUCCESS) { + warningx("ldap_sasl_interactive_bind_s(): %s", ldap_err2string(rc)); + return(-1); + } + DPRINTF(("ldap_sasl_interactive_bind_s() ok"), 1); + } else +#endif /* HAVE_LDAP_SASL_INTERACTIVE_BIND_S */ +#ifdef HAVE_LDAP_SASL_BIND_S { - if (ldap_conf.debug) printf("found:%s\n",ldap_get_dn(ld,entry)); - if ( - /* first verify user netgroup matches - only if in pass 2 */ - (pass!=2 || sudo_ldap_check_user_netgroup(ld,entry)) && - /* remember that user matched */ - (ldap_user_matches=-1) && - /* verify host match */ - sudo_ldap_check_host(ld,entry) && - /* remember that host matched */ - (ldap_host_matches=-1) && - /* add matches for listing later */ - sudo_ldap_add_match(ld,entry) && - /* verify command match */ - sudo_ldap_check_command(ld,entry) && - /* verify runas match */ - sudo_ldap_check_runas(ld,entry) - ) - { - /* We have a match! */ - if(ldap_conf.debug) printf("Perfect Matched!\n"); - /* pick up any options */ - sudo_ldap_parse_options(ld,entry); - /* make sure we dont reenter loop */ - ret=VALIDATE_OK; - /* break from inside for loop */ - break; - } - - } - if (result) ldap_msgfree(result); - result=NULL; - - } - - /* shut down connection */ - if (ld) ldap_unbind_s(ld); - - - if (ldap_conf.debug) printf("user_matches=%d\n",ldap_user_matches); - if (ldap_conf.debug) printf("host_matches=%d\n",ldap_host_matches); - - /* Check for special case for -v, -k, -l options */ - if (pwflag && ldap_user_matches && ldap_host_matches){ + struct berval bv; + + bv.bv_val = ldap_conf.bindpw ? ldap_conf.bindpw : ""; + bv.bv_len = strlen(bv.bv_val); + + rc = ldap_sasl_bind_s(ld, ldap_conf.binddn, LDAP_SASL_SIMPLE, &bv, + NULL, NULL, NULL); + if (rc != LDAP_SUCCESS) { + warningx("ldap_sasl_bind_s(): %s", ldap_err2string(rc)); + return(-1); + } + DPRINTF(("ldap_sasl_bind_s() ok"), 1); + } +#else + { + rc = ldap_simple_bind_s(ld, ldap_conf.binddn, ldap_conf.bindpw); + if (rc != LDAP_SUCCESS) { + warningx("ldap_simple_bind_s(): %s", ldap_err2string(rc)); + return(-1); + } + DPRINTF(("ldap_simple_bind_s() ok"), 1); + } +#endif + return(0); +} + +/* + * Open a connection to the LDAP server. + * Returns 0 on success and non-zero on failure. + */ +int +sudo_ldap_open(nss) + struct sudo_nss *nss; +{ + LDAP *ld; + int rc, ldapnoinit = FALSE; + + if (!sudo_ldap_read_config()) + return(-1); + + /* Prevent reading of user ldaprc and system defaults. */ + if (getenv("LDAPNOINIT") == NULL) { + ldapnoinit = TRUE; + setenv("LDAPNOINIT", "1", TRUE); + } + + /* Connect to LDAP server */ +#ifdef HAVE_LDAP_INITIALIZE + if (ldap_conf.uri != NULL) { + DPRINTF(("ldap_initialize(ld, %s)", ldap_conf.uri), 2); + rc = ldap_initialize(&ld, ldap_conf.uri); + } else +#endif + rc = sudo_ldap_init(&ld, ldap_conf.host, ldap_conf.port); + if (rc != LDAP_SUCCESS) { + warningx("unable to initialize LDAP: %s", ldap_err2string(rc)); + return(-1); + } + + if (ldapnoinit) + unsetenv("LDAPNOINIT"); + + /* Set LDAP options */ + if (sudo_ldap_set_options(ld) < 0) + return(-1); + + if (ldap_conf.ssl_mode == SUDO_LDAP_STARTTLS) { +#if defined(HAVE_LDAP_START_TLS_S) + rc = ldap_start_tls_s(ld, NULL, NULL); + if (rc != LDAP_SUCCESS) { + warningx("ldap_start_tls_s(): %s", ldap_err2string(rc)); + return(-1); + } + DPRINTF(("ldap_start_tls_s() ok"), 1); +#elif defined(HAVE_LDAP_SSL_CLIENT_INIT) && defined(HAVE_LDAP_START_TLS_S_NP) + if (ldap_ssl_client_init(NULL, NULL, 0, &rc) != LDAP_SUCCESS) { + warningx("ldap_ssl_client_init(): %s", ldap_err2string(rc)); + return(-1); + } + rc = ldap_start_tls_s_np(ld, NULL); + if (rc != LDAP_SUCCESS) { + warningx("ldap_start_tls_s_np(): %s", ldap_err2string(rc)); + return(-1); + } + DPRINTF(("ldap_start_tls_s_np() ok"), 1); +#else + warningx("start_tls specified but LDAP libs do not support ldap_start_tls_s() or ldap_start_tls_s_np()"); +#endif /* !HAVE_LDAP_START_TLS_S && !HAVE_LDAP_START_TLS_S_NP */ + } + + /* Actually connect */ + if (sudo_ldap_bind_s(ld) != 0) + return(-1); + + nss->handle = ld; + return(0); +} + +int +sudo_ldap_setdefs(nss) + struct sudo_nss *nss; +{ + LDAP *ld = (LDAP *) nss->handle; + LDAPMessage *entry = NULL, *result = NULL; /* used for searches */ + int rc; /* temp return value */ + + if (ld == NULL) + return(-1); + + rc = ldap_search_ext_s(ld, ldap_conf.base, LDAP_SCOPE_SUBTREE, + "cn=defaults", NULL, 0, NULL, NULL, NULL, 0, &result); + if (rc == 0 && (entry = ldap_first_entry(ld, result))) { + DPRINTF(("found:%s", ldap_get_dn(ld, entry)), 1); + sudo_ldap_parse_options(ld, entry); + } else + DPRINTF(("no default options found!"), 1); + + if (result) + ldap_msgfree(result); + + return(0); +} + +/* + * like sudoers_lookup() - only LDAP style + */ +int +sudo_ldap_lookup(nss, ret, pwflag) + struct sudo_nss *nss; + int ret; + int pwflag; +{ + LDAP *ld = (LDAP *) nss->handle; + LDAPMessage *entry = NULL, *result = NULL; + char *filt; + int do_netgr, rc, matched; + int setenv_implied; + int ldap_user_matches = FALSE, ldap_host_matches = FALSE; + struct passwd *pw = list_pw ? list_pw : sudo_user.pw; + + if (ld == NULL) + return(ret); + + if (pwflag) { + int doauth = UNSPEC; + enum def_tupple pwcheck = + (pwflag == -1) ? never : sudo_defs_table[pwflag].sd_un.tuple; + + for (matched = 0, do_netgr = 0; !matched && do_netgr < 2; do_netgr++) { + filt = do_netgr ? estrdup("sudoUser=+*") : sudo_ldap_build_pass1(pw); + rc = ldap_search_ext_s(ld, ldap_conf.base, LDAP_SCOPE_SUBTREE, filt, + NULL, 0, NULL, NULL, NULL, 0, &result); + efree(filt); + if (rc != LDAP_SUCCESS) + continue; + + LDAP_FOREACH(entry, ld, result) { + /* only verify netgroup matches in pass 2 */ + if (do_netgr && !sudo_ldap_check_user_netgroup(ld, entry, pw->pw_name)) + continue; + + ldap_user_matches = TRUE; + if (sudo_ldap_check_host(ld, entry)) { + ldap_host_matches = TRUE; + if ((pwcheck == any && doauth != FALSE) || + (pwcheck == all && doauth == FALSE)) + doauth = sudo_ldap_check_bool(ld, entry, "authenticate"); + /* Only check the command when listing another user. */ + if (user_uid == 0 || list_pw == NULL || + user_uid == list_pw->pw_uid || + sudo_ldap_check_command(ld, entry, NULL)) { + matched = 1; + break; /* end foreach */ + } + } + } + ldap_msgfree(result); + result = NULL; + } + if (matched || user_uid == 0) { + SET(ret, VALIDATE_OK); + CLR(ret, VALIDATE_NOT_OK); + if (def_authenticate) { + switch (pwcheck) { + case always: + SET(ret, FLAG_CHECK_USER); + break; + case all: + case any: + if (doauth == FALSE) + def_authenticate = FALSE; + break; + case never: + def_authenticate = FALSE; + break; + default: + break; + } + } + } + goto done; + } + /* - * Handle verifypw & listpw + * Okay - time to search for anything that matches this user + * Lets limit it to only two queries of the LDAP server * - * To be extra paranoid, since we haven't read any NOPASSWD options - * in /etc/sudoers yet, but we have to make the decission now, lets - * assume the worst and prefer to prompt for password unless the setting - * is "never". (example verifypw=never or listpw=never) + * The first pass will look by the username, groups, and + * the keyword ALL. We will then inspect the results that + * came back from the query. We don't need to inspect the + * sudoUser in this pass since the LDAP server already scanned + * it for us. * + * The second pass will return all the entries that contain + * user netgroups. Then we take the netgroups returned and + * try to match them against the username. */ - if (pwflag<0) { /* -k */ - ret=VALIDATE_OK; SET(ret,FLAG_NOPASS); - } else if (sudo_defs_table[pwflag].sd_un.tuple == never){ /* see note above */ - ret=VALIDATE_OK; SET(ret,FLAG_NOPASS); - } else { - ret=VALIDATE_OK; /* extra paranoid */ - } - } - - if (ISSET(ret,VALIDATE_OK)) { - /* We have a match. Should we check the password? */ - /* Note: This could be the global or a rule specific option */ - if (!def_authenticate) SET(ret,FLAG_NOPASS); - /* Same logic with noexec */ - if (def_noexec) SET(ret,FLAG_NOEXEC); - } else { - /* we do not have a match */ - ret=VALIDATE_NOT_OK; - if (!ldap_user_matches) SET(ret,FLAG_NO_USER); - else if (!ldap_host_matches) SET(ret,FLAG_NO_HOST); - } - - if (ldap_conf.debug) printf("sudo_ldap_check(%d)=0x%02x\n",pwflag,ret); - - return ret ; + setenv_implied = FALSE; + for (matched = 0, do_netgr = 0; !matched && do_netgr < 2; do_netgr++) { + filt = do_netgr ? estrdup("sudoUser=+*") : sudo_ldap_build_pass1(pw); + DPRINTF(("ldap search '%s'", filt), 1); + rc = ldap_search_ext_s(ld, ldap_conf.base, LDAP_SCOPE_SUBTREE, filt, + NULL, 0, NULL, NULL, NULL, 0, &result); + if (rc != LDAP_SUCCESS) + DPRINTF(("nothing found for '%s'", filt), 1); + efree(filt); + + /* parse each entry returned from this most recent search */ + if (rc == LDAP_SUCCESS) { + LDAP_FOREACH(entry, ld, result) { + DPRINTF(("found:%s", ldap_get_dn(ld, entry)), 1); + if ( + /* first verify user netgroup matches - only if in pass 2 */ + (!do_netgr || sudo_ldap_check_user_netgroup(ld, entry, pw->pw_name)) && + /* remember that user matched */ + (ldap_user_matches = TRUE) && + /* verify host match */ + sudo_ldap_check_host(ld, entry) && + /* remember that host matched */ + (ldap_host_matches = TRUE) && + /* verify runas match */ + sudo_ldap_check_runas(ld, entry) && + /* verify command match */ + (rc = sudo_ldap_check_command(ld, entry, &setenv_implied)) != UNSPEC + ) { + /* We have a match! */ + DPRINTF(("Command %sallowed", rc == TRUE ? "" : "NOT "), 1); + matched = TRUE; + if (rc == TRUE) { + /* pick up any options */ + if (setenv_implied) + def_setenv = TRUE; + sudo_ldap_parse_options(ld, entry); +#ifdef HAVE_SELINUX + /* Set role and type if not specified on command line. */ + if (user_role == NULL) + user_role = def_role; + if (user_type == NULL) + user_type = def_type; +#endif /* HAVE_SELINUX */ + /* make sure we don't reenter loop */ + SET(ret, VALIDATE_OK); + CLR(ret, VALIDATE_NOT_OK); + } else { + SET(ret, VALIDATE_NOT_OK); + CLR(ret, VALIDATE_OK); + } + /* break from inside for loop */ + break; + } + } + ldap_msgfree(result); + result = NULL; + } + } + +done: + DPRINTF(("user_matches=%d", ldap_user_matches), 1); + DPRINTF(("host_matches=%d", ldap_host_matches), 1); + + if (!ISSET(ret, VALIDATE_OK)) { + /* we do not have a match */ + if (pwflag && list_pw == NULL) + SET(ret, FLAG_NO_CHECK); + } + if (ldap_user_matches) + CLR(ret, FLAG_NO_USER); + if (ldap_host_matches) + CLR(ret, FLAG_NO_HOST); + DPRINTF(("sudo_ldap_lookup(%d)=0x%02x", pwflag, ret), 1); + + return(ret); +} + +/* + * shut down LDAP connection + */ +int +sudo_ldap_close(nss) + struct sudo_nss *nss; +{ + if (nss->handle != NULL) { + ldap_unbind_ext_s((LDAP *) nss->handle, NULL, NULL); + nss->handle = NULL; + } + return(0); +} + +/* + * STUB + */ +int +sudo_ldap_parse(nss) + struct sudo_nss *nss; +{ + return(0); }