diff options
Diffstat (limited to 'usr/src/lib/libsldap/common/ns_sasl.c')
-rw-r--r-- | usr/src/lib/libsldap/common/ns_sasl.c | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/usr/src/lib/libsldap/common/ns_sasl.c b/usr/src/lib/libsldap/common/ns_sasl.c new file mode 100644 index 0000000000..fc708b735a --- /dev/null +++ b/usr/src/lib/libsldap/common/ns_sasl.c @@ -0,0 +1,580 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <thread.h> +#include <synch.h> +#include <sasl/sasl.h> +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <syslog.h> +#include <ctype.h> +#include <libscf.h> +#include <libintl.h> +#include <locale.h> +#include "ns_sldap.h" +#include "ns_internal.h" + +static int self_gssapi_only = 0; +static mutex_t self_gssapi_only_lock = DEFAULTMUTEX; + +#define DNS_FMRI "svc:/network/dns/client:default" +#define MSGSIZE 256 + +#define NSSWITCH_CONF "/etc/nsswitch.conf" + +/* + * Error Handling + */ +#define CLIENT_FPRINTF if (mode_verbose && !mode_quiet) (void) fprintf + +/* + * One time initializtion + */ +int sasl_gssapi_inited = 0; +static mutex_t sasl_gssapi_lock = DEFAULTMUTEX; +int +__s_api_sasl_gssapi_init(void) { + int rc = NS_LDAP_SUCCESS; + (void) mutex_lock(&sasl_gssapi_lock); + if (!sasl_gssapi_inited) { + if (getuid() == 0) { + if (system( + "/usr/sbin/cryptoadm disable metaslot") + == 0) { + syslog(LOG_WARNING, + "libsldap: Metaslot disabled " + "for self credential mode"); + sasl_gssapi_inited = 1; + } else { + syslog(LOG_ERR, + "libsldap: Can't disable " + "Metaslot for self credential " + "mode"); + rc = NS_LDAP_INTERNAL; + } + } + } + (void) mutex_unlock(&sasl_gssapi_lock); + + return (rc); +} + +/* + * nscd calls this function to set self_gssapi_only flag so libsldap performs + * sasl/GSSAPI bind only. Also see comments of __ns_ldap_self_gssapi_config. + * + * Input: flag 0 use any kind of connection + * 1 use self/gssapi connection only + */ +void +__ns_ldap_self_gssapi_only_set(int flag) { + (void) mutex_lock(&self_gssapi_only_lock); + self_gssapi_only = flag; + (void) mutex_unlock(&self_gssapi_only_lock); +} +/* + * Get the flag value of self_gssapi_only + */ +int +__s_api_self_gssapi_only_get(void) { + int flag; + (void) mutex_lock(&self_gssapi_only_lock); + flag = self_gssapi_only; + (void) mutex_unlock(&self_gssapi_only_lock); + return (flag); +} +/* + * nscd calls this function to detect the current native ldap configuration. + * The output are + * NS_LDAP_SELF_GSSAPI_CONFIG_NONE: No credential level self and + * no authentication method sasl/GSSAPI is + * configured. + * NS_LDAP_SELF_GSSAPI_CONFIG_ONLY: Only credential level self and + * authentication method sasl/GSSAPI are + * configured. + * NS_LDAP_SELF_GSSAPI_CONFIG_MIXED: More than one credential level are + * configured, including self. + * More than one authentication method + * are configured, including sasl/GSSAPI. + * + * __s_api_crosscheck makes sure self and sasl/GSSAPI pair up if they do + * get configured. + * + * When nscd detects it's MIXED case, it calls __ns_ldap_self_gssapi_only_set + * to force libsldap to do sasl/GSSAPI bind only for per-user lookup. + * + * Return: NS_LDAP_SUCCESS + * OTHERWISE - FAILURE + * + * Output: config. See comments above. + * + */ +int +__ns_ldap_self_gssapi_config(ns_ldap_self_gssapi_config_t *config) { + int self = 0, other_level = 0, gssapi = 0, other_method = 0; + ns_auth_t **aMethod = NULL, **aNext = NULL; + int **cLevel = NULL, **cNext = NULL, rc; + ns_ldap_error_t *errp = NULL; + + if (config == NULL) + return (NS_LDAP_INVALID_PARAM); + else + *config = NS_LDAP_SELF_GSSAPI_CONFIG_NONE; + + /* Get the credential level list */ + if ((rc = __ns_ldap_getParam(NS_LDAP_CREDENTIAL_LEVEL_P, + (void ***)&cLevel, &errp)) != NS_LDAP_SUCCESS) { + if (errp) + (void) __ns_ldap_freeError(&errp); + if (cLevel) + (void) __ns_ldap_freeParam((void ***)&cLevel); + return (rc); + } + if (errp) + (void) __ns_ldap_freeError(&errp); + /* Get the authentication method list */ + if ((rc = __ns_ldap_getParam(NS_LDAP_AUTH_P, + (void ***)&aMethod, &errp)) != NS_LDAP_SUCCESS) { + if (errp) + (void) __ns_ldap_freeError(&errp); + if (cLevel) + (void) __ns_ldap_freeParam((void ***)&cLevel); + if (aMethod) + (void) __ns_ldap_freeParam((void ***)&aMethod); + return (rc); + } + if (errp) + (void) __ns_ldap_freeError(&errp); + + if (cLevel == NULL || aMethod == NULL) { + if (cLevel) + (void) __ns_ldap_freeParam((void ***)&cLevel); + if (aMethod) + (void) __ns_ldap_freeParam((void ***)&aMethod); + return (NS_LDAP_SUCCESS); + } + + for (cNext = cLevel; *cNext != NULL; cNext++) { + if (**cNext == NS_LDAP_CRED_SELF) + self++; + else + other_level++; + } + for (aNext = aMethod; *aNext != NULL; aNext++) { + if ((*aNext)->saslmech == NS_LDAP_SASL_GSSAPI) + gssapi++; + else + other_method++; + } + + if (self > 0 && gssapi > 0) { + if (other_level == 0 && other_method == 0) + *config = NS_LDAP_SELF_GSSAPI_CONFIG_ONLY; + else + *config = NS_LDAP_SELF_GSSAPI_CONFIG_MIXED; + } + + if (cLevel) + (void) __ns_ldap_freeParam((void ***)&cLevel); + if (aMethod) + (void) __ns_ldap_freeParam((void ***)&aMethod); + return (NS_LDAP_SUCCESS); +} + +int +__s_api_sasl_bind_callback( + /* LINTED E_FUNC_ARG_UNUSED */ + LDAP *ld, + /* LINTED E_FUNC_ARG_UNUSED */ + unsigned flags, + void *defaults, + void *in) +{ + char *ret = NULL; + sasl_interact_t *interact = in; + ns_sasl_cb_param_t *cred = (ns_sasl_cb_param_t *)defaults; + + + while (interact->id != SASL_CB_LIST_END) { + + switch (interact->id) { + + case SASL_CB_GETREALM: + ret = cred->realm; + break; + case SASL_CB_AUTHNAME: + ret = cred->authid; + break; + case SASL_CB_PASS: + ret = cred->passwd; + break; + case SASL_CB_USER: + ret = cred->authzid; + break; + case SASL_CB_NOECHOPROMPT: + case SASL_CB_ECHOPROMPT: + default: + break; + } + + if (ret) { + interact->result = strdup(ret); + if (interact->result == NULL) + return (LDAP_NO_MEMORY); + + interact->len = strlen(ret); + } else { + interact->result = NULL; + interact->len = 0; + } + interact++; + } + + return (LDAP_SUCCESS); +} + +/* + * Find "dbase: service1 [...] services2" in fname and return + * " service1 [...] services2" + * e.g. + * Find "hosts: files dns" and return " files dns" + */ +static char * +__ns_nsw_getconfig(const char *dbase, const char *fname, int *errp) +{ + FILE *fp = NULL; + char *linep, *retp = NULL; + char lineq[BUFSIZ], db_colon[BUFSIZ]; + + if ((fp = fopen(fname, "rF")) == NULL) { + *errp = NS_LDAP_CONFIG; + return (NULL); + } + *errp = NS_LDAP_SUCCESS; + + while (linep = fgets(lineq, BUFSIZ, fp)) { + char *tokenp, *comment; + + /* + * Ignore portion of line following the comment character '#'. + */ + if ((comment = strchr(linep, '#')) != NULL) { + *comment = '\0'; + } + if ((*linep == '\0') || isspace(*linep)) { + continue; + } + (void) snprintf(db_colon, BUFSIZ, "%s:", dbase); + if ((tokenp = strstr(linep, db_colon)) == NULL) { + continue; /* ignore this line */ + } else { + /* skip "dbase:" */ + retp = strdup(tokenp + strlen(db_colon)); + if (retp == NULL) + *errp = NS_LDAP_MEMORY; + } + } + + (void) fclose(fp); + return (retp); +} +/* + * Test the configurations of the "hosts" and "ipnodes" + * dns has to be present and appear before ldap + * e.g. + * "dns" , "dns files" "dns ldap files", "files dns" are allowed. + * + * Kerberos requires dns or it'd fail. + */ +static int +test_dns_nsswitch(int foreground, + const char *fname, + ns_ldap_error_t **errpp) { + int ldap, dns, i, pserr, rc = NS_LDAP_SUCCESS; + char *db[3] = {"hosts", "ipnodes", NULL}; + char buf[MSGSIZE], *conf = NULL, *token = NULL, *last = NULL; + + for (i = 0; db[i] != NULL; i++) { + conf = __ns_nsw_getconfig(db[i], fname, &pserr); + + if (conf == NULL) { + (void) snprintf(buf, MSGSIZE, + gettext("Parsing %s to find \"%s:\" " + "failed. err: %d"), + fname, db[i], pserr); + if (foreground) { + (void) fprintf(stderr, "%s\n", buf); + } else { + MKERROR(LOG_ERR, *errpp, NS_LDAP_CONFIG, + strdup(buf), NS_LDAP_MEMORY); + } + return (pserr); + } + ldap = dns = 0; + token = strtok_r(conf, " ", &last); + while (token != NULL) { + if (strncmp(token, "dns", 3) == 0) { + if (ldap) { + (void) snprintf(buf, MSGSIZE, + gettext("%s: ldap can't appear " + "before dns"), db[i]); + if (foreground) { + (void) fprintf(stderr, + "start: %s\n", + buf); + } else { + MKERROR(LOG_ERR, *errpp, + NS_LDAP_CONFIG, + strdup(buf), + NS_LDAP_MEMORY); + } + free(conf); + return (NS_LDAP_CONFIG); + } else { + dns++; + } + } else if (strncmp(token, "ldap", 4) == 0) { + ldap++; + } + /* next token */ + token = strtok_r(NULL, " ", &last); + } + if (conf) { + free(conf); + conf = NULL; + } + if (!dns) { + (void) snprintf(buf, MSGSIZE, + gettext("%s: dns is not defined in " + "%s"), db[i], fname); + if (foreground) { + (void) fprintf(stderr, "start: %s\n", buf); + } else { + MKERROR(LOG_ERR, *errpp, NS_LDAP_CONFIG, + strdup(buf), NS_LDAP_MEMORY); + } + rc = NS_LDAP_CONFIG; + break; + } + } + return (rc); +} + +static boolean_t +is_service(const char *fmri, const char *state) { + char *st; + boolean_t result = B_FALSE; + + if ((st = smf_get_state(fmri)) != NULL) { + if (strcmp(st, state) == 0) + result = B_TRUE; + free(st); + } + return (result); +} + + +/* + * This function checks dns prerequisites for sasl/GSSAPI bind. + * It's called only if config == NS_LDAP_SELF_GSSAPI_CONFIG_ONLY || + * config == NS_LDAP_SELF_GSSAPI_CONFIG_MIXED. + */ +int +__ns_ldap_check_dns_preq(int foreground, + int mode_verbose, + int mode_quiet, + const char *fname, + ns_ldap_self_gssapi_config_t config, + ns_ldap_error_t **errpp) { + + char buf[MSGSIZE]; + int retcode = NS_LDAP_SUCCESS; + int loglevel; + + if (errpp) + *errpp = NULL; + else + return (NS_LDAP_INVALID_PARAM); + + if (config == NS_LDAP_SELF_GSSAPI_CONFIG_NONE) + /* Shouldn't happen. Check this value just in case */ + return (NS_LDAP_SUCCESS); + + if ((retcode = test_dns_nsswitch(foreground, fname, errpp)) != + NS_LDAP_SUCCESS) + return (retcode); + + if (is_service(DNS_FMRI, SCF_STATE_STRING_ONLINE)) { + if (foreground) { + CLIENT_FPRINTF(stdout, "start: %s\n", + gettext("DNS client is enabled")); + } else { + syslog(LOG_INFO, "%s", + gettext("DNS client is enabled")); + } + return (NS_LDAP_SUCCESS); + } else { + if (config == NS_LDAP_SELF_GSSAPI_CONFIG_ONLY) { + (void) snprintf(buf, MSGSIZE, + gettext("%s: DNS client is not enabled. " + "Run \"svcadm enable %s\". %s."), + "Error", DNS_FMRI, "Abort"); + loglevel = LOG_ERR; + retcode = NS_LDAP_CONFIG; + } else if (config == NS_LDAP_SELF_GSSAPI_CONFIG_MIXED) { + (void) snprintf(buf, MSGSIZE, + gettext("%s: DNS client is not enabled. " + "Run \"svcadm enable %s\". %s." + "Fall back to other cred level/bind. "), + "Warning", DNS_FMRI, "Continue"); + loglevel = LOG_INFO; + retcode = NS_LDAP_SUCCESS; + } + + if (foreground) { + (void) fprintf(stderr, "start: %s\n", buf); + } else { + MKERROR(loglevel, *errpp, retcode, strdup(buf), + NS_LDAP_MEMORY); + } + return (retcode); + } +} + +/* + * Check if sasl/GSSAPI works + */ +int +__ns_ldap_check_gssapi_preq(int foreground, + int mode_verbose, + int mode_quiet, + ns_ldap_self_gssapi_config_t config, + ns_ldap_error_t **errpp) { + + int rc; + char *attr[2] = {"dn", NULL}, buf[MSGSIZE]; + ns_cred_t cred; + ns_ldap_result_t *result = NULL; + int loglevel; + + if (errpp) + *errpp = NULL; + else + return (NS_LDAP_INVALID_PARAM); + + if (config == NS_LDAP_SELF_GSSAPI_CONFIG_NONE) + /* Don't need to check */ + return (NS_LDAP_SUCCESS); + + (void) memset(&cred, 0, sizeof (ns_cred_t)); + + cred.auth.type = NS_LDAP_AUTH_SASL; + cred.auth.tlstype = NS_LDAP_TLS_NONE; + cred.auth.saslmech = NS_LDAP_SASL_GSSAPI; + + rc = __ns_ldap_list(NULL, (const char *)"objectclass=*", + NULL, (const char **)attr, &cred, + NS_LDAP_SCOPE_BASE, &result, errpp, NULL, NULL); + if (result) + (void) __ns_ldap_freeResult(&result); + + if (rc == NS_LDAP_SUCCESS) { + if (foreground) { + CLIENT_FPRINTF(stdout, "start: %s\n", + gettext("sasl/GSSAPI bind works")); + } else { + syslog(LOG_INFO, "%s", + gettext("sasl/GSSAPI bind works")); + } + return (NS_LDAP_SUCCESS); + } else { + if (config == NS_LDAP_SELF_GSSAPI_CONFIG_ONLY) { + (void) snprintf(buf, MSGSIZE, + gettext("%s: sasl/GSSAPI bind is not " + "working. %s."), + "Error", "Abort"); + loglevel = LOG_ERR; + } else if (config == NS_LDAP_SELF_GSSAPI_CONFIG_MIXED) { + (void) snprintf(buf, MSGSIZE, + gettext("%s: sasl/GSSAPI bind is not " + "working. Fall back to other cred " + "level/bind. %s."), + "Warning", "Continue"); + loglevel = LOG_INFO; + /* reset return code */ + rc = NS_LDAP_SUCCESS; + } + + if (foreground) { + (void) fprintf(stderr, "start: %s\n", buf); + } else { + MKERROR(loglevel, *errpp, rc, strdup(buf), + NS_LDAP_MEMORY); + } + return (rc); + } +} +/* + * This is called by ldap_cachemgr to check dns and gssapi prequisites. + */ +int +__ns_ldap_check_all_preq(int foreground, + int mode_verbose, + int mode_quiet, + ns_ldap_self_gssapi_config_t config, + ns_ldap_error_t **errpp) { + + int rc; + + if (errpp) + *errpp = NULL; + else + return (NS_LDAP_INVALID_PARAM); + + if (config == NS_LDAP_SELF_GSSAPI_CONFIG_NONE) + /* Don't need to check */ + return (NS_LDAP_SUCCESS); + + if ((rc = __ns_ldap_check_dns_preq(foreground, + mode_verbose, mode_quiet, NSSWITCH_CONF, + config, errpp)) != NS_LDAP_SUCCESS) + return (rc); + if ((rc = __ns_ldap_check_gssapi_preq(foreground, + mode_verbose, mode_quiet, config, errpp)) != + NS_LDAP_SUCCESS) + return (rc); + + return (NS_LDAP_SUCCESS); +} |