summaryrefslogtreecommitdiffstats
path: root/ldap/servers/slapd/ldaputil.c
diff options
context:
space:
mode:
Diffstat (limited to 'ldap/servers/slapd/ldaputil.c')
-rw-r--r--ldap/servers/slapd/ldaputil.c1529
1 files changed, 1529 insertions, 0 deletions
diff --git a/ldap/servers/slapd/ldaputil.c b/ldap/servers/slapd/ldaputil.c
new file mode 100644
index 00000000..4ea56143
--- /dev/null
+++ b/ldap/servers/slapd/ldaputil.c
@@ -0,0 +1,1529 @@
+/** BEGIN COPYRIGHT BLOCK
+ * This Program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; version 2 of the License.
+ *
+ * This Program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link to the code of this Program
+ * through those well defined interfaces identified in the file named EXCEPTION
+ * found in the source code files (the "Approved Interfaces"). The files of
+ * Non-GPL Code may instantiate templates or use macros or inline functions from
+ * the Approved Interfaces without causing the resulting work to be covered by
+ * the GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code used
+ * in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish to
+ * provide this exception without modification, you must delete this exception
+ * statement from your version and license this file solely under the GPL without
+ * exception.
+ *
+ *
+ * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
+ * Copyright (C) 2005 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* ldaputil.c -- LDAP utility functions and wrappers */
+#ifdef _WIN32
+#include <direct.h> /* for getcwd */
+#else
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <pwd.h>
+#endif
+#include <libgen.h>
+#include <pk11func.h>
+#include "slap.h"
+#include "prtime.h"
+#include "prinrval.h"
+#include "snmp_collator.h"
+#if !defined(USE_OPENLDAP)
+#include <ldap_ssl.h>
+#include <ldappr.h>
+#endif
+
+#ifdef MEMPOOL_EXPERIMENTAL
+void _free_wrapper(void *ptr)
+{
+ slapi_ch_free(&ptr);
+}
+#endif
+
+/*
+ * Function: slapi_ldap_unbind()
+ * Purpose: release an LDAP session obtained from a call to slapi_ldap_init().
+ */
+void
+slapi_ldap_unbind( LDAP *ld )
+{
+ if ( ld != NULL ) {
+ ldap_unbind_ext( ld, NULL, NULL );
+ }
+}
+
+const char *
+slapi_urlparse_err2string( int err )
+{
+ const char *s="internal error";
+
+ switch( err ) {
+ case 0:
+ s = "no error";
+ break;
+ case LDAP_URL_ERR_BADSCOPE:
+ s = "invalid search scope";
+ break;
+ case LDAP_URL_ERR_MEM:
+ s = "unable to allocate memory";
+ break;
+ case LDAP_URL_ERR_PARAM:
+ s = "bad parameter to an LDAP URL function";
+ break;
+#if defined(USE_OPENLDAP)
+ case LDAP_URL_ERR_BADSCHEME:
+ s = "does not begin with ldap://, ldaps://, or ldapi://";
+ break;
+ case LDAP_URL_ERR_BADENCLOSURE:
+ s = "missing trailing '>' in enclosure";
+ break;
+ case LDAP_URL_ERR_BADURL:
+ s = "not a valid LDAP URL";
+ break;
+ case LDAP_URL_ERR_BADHOST:
+ s = "hostname part of url is not valid or not given";
+ break;
+ case LDAP_URL_ERR_BADATTRS:
+ s = "attribute list not formatted correctly or missing";
+ break;
+ case LDAP_URL_ERR_BADFILTER:
+ s = "search filter not correct";
+ break;
+ case LDAP_URL_ERR_BADEXTS:
+ s = "extensions not specified correctly";
+ break;
+#else /* !USE_OPENLDAP */
+ case LDAP_URL_ERR_NOTLDAP:
+ s = "missing ldap:// or ldaps:// or ldapi://";
+ break;
+ case LDAP_URL_ERR_NODN:
+ s = "missing suffix";
+ break;
+#endif
+ }
+
+ return( s );
+}
+
+/* there are various differences among url parsers - directory server
+ needs the ability to parse partial URLs - those with no dn - and
+ needs to be able to tell if it is a secure url (ldaps) or not */
+int
+slapi_ldap_url_parse(const char *url, LDAPURLDesc **ludpp, int require_dn, int *secure)
+{
+ PR_ASSERT(url);
+ PR_ASSERT(ludpp);
+ int rc;
+
+ if (secure) {
+ *secure = 0;
+ }
+#if defined(HAVE_LDAP_URL_PARSE_NO_DEFAULTS)
+ rc = ldap_url_parse_no_defaults(url, ludpp, require_dn);
+ if (!rc && *ludpp && secure) {
+ *secure = (*ludpp)->lud_options & LDAP_URL_OPT_SECURE;
+ }
+#else /* openldap */
+#if defined(HAVE_LDAP_URL_PARSE_EXT)
+ rc = ldap_url_parse_ext(url, ludpp, require_dn ? LDAP_PVT_URL_PARSE_NONE : LDAP_PVT_URL_PARSE_NOEMPTY_DN);
+#else
+ rc = ldap_url_parse(url, ludpp);
+ if (rc || !*ludpp || !require_dn) { /* failed - see if failure was due to missing dn */
+ size_t len = strlen(url);
+ /* assume the url is just scheme://host:port[/] - add the empty string
+ as the DN (adding a trailing / first if needed) and try to parse
+ again
+ */
+ char *urlcopy = slapi_ch_smprintf("%s%s%s", url, (url[len-1] == '/' ? "" : "/"), "");
+ rc = ldap_url_parse(urlcopy, ludpp);
+ slapi_ch_free_string(&urlcopy);
+ if (0 == rc) { /* only problem was the DN - free it */
+ slapi_ch_free_string(&((*ludpp)->lud_dn));
+ }
+ }
+#endif
+ if (!rc && *ludpp && secure) {
+ *secure = (*ludpp)->lud_scheme && !strcmp((*ludpp)->lud_scheme, "ldaps");
+ }
+#endif /* openldap */
+
+ return rc;
+}
+
+#include <sasl.h>
+
+int
+slapi_ldap_get_lderrno(LDAP *ld, char **m, char **s)
+{
+ int rc = LDAP_SUCCESS;
+
+#if defined(USE_OPENLDAP)
+ ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &rc);
+ if (m) {
+ ldap_get_option(ld, LDAP_OPT_MATCHED_DN, m);
+ }
+ if (s) {
+#ifdef LDAP_OPT_DIAGNOSTIC_MESSAGE
+ ldap_get_option(ld, LDAP_OPT_DIAGNOSTIC_MESSAGE, s);
+#else
+ ldap_get_option(ld, LDAP_OPT_ERROR_STRING, s);
+#endif
+ }
+#else /* !USE_OPENLDAP */
+ rc = ldap_get_lderrno( ld, m, s );
+#endif
+ return rc;
+}
+
+void
+slapi_ldif_put_type_and_value_with_options( char **out, const char *t, const char *val, int vlen, unsigned long options )
+{
+#if defined(USE_OPENLDAP)
+ /* openldap always wraps and always does conservative base64 encoding
+ we unwrap here, but clients will have to do their own base64 decode */
+ int type = LDIF_PUT_VALUE;
+ char *save = *out;
+
+ if (options & LDIF_OPT_VALUE_IS_URL) {
+ type = LDIF_PUT_URL;
+ }
+ ldif_sput( out, type, t, val, vlen );
+ if (options & LDIF_OPT_NOWRAP) {
+ /* modify out in place, stripping out continuation lines */
+ char *src = save;
+ char *dest = save;
+ for (; src && *src && (src != *out); ++src) {
+ if (!strncmp(src, "\n ", 2)) {
+ src += 2; /* skip continuation */
+ }
+ *dest++ = *src;
+ }
+ *dest = '\n';
+ }
+#else
+ ldif_put_type_and_value_with_options( out, (char *)t, (char *)val, vlen, options );
+#endif
+}
+
+void
+slapi_ldap_value_free( char **vals )
+{
+#if defined(USE_OPENLDAP)
+ slapi_ch_array_free(vals);
+#else
+ ldap_value_free(vals);
+#endif
+}
+
+int
+slapi_ldap_count_values( char **vals )
+{
+#if defined(USE_OPENLDAP)
+ return ldap_count_values_len((struct berval **)vals);
+#else
+ return ldap_count_values(vals);
+#endif
+}
+
+/*
+ Perform LDAP init and return an LDAP* handle. If ldapurl is given,
+ that is used as the basis for the protocol, host, port, and whether
+ to use starttls (given on the end as ldap://..../?????starttlsOID
+ If hostname is given, LDAP or LDAPS is assumed, and this will override
+ the hostname from the ldapurl, if any. If port is > 0, this is the
+ port number to use. It will override the port in the ldapurl, if any.
+ If no port is given in port or ldapurl, the default will be used based
+ on the secure setting (389 for ldap, 636 for ldaps, 389 for starttls)
+ secure takes 1 of 3 values - 0 means regular ldap, 1 means ldaps, 2
+ means regular ldap with starttls.
+ filename is the ldapi file name - if this is given, and no other options
+ are given, ldapi is assumed.
+ */
+/* util_sasl_path: the string argument for putenv.
+ It must be a global or a static */
+char util_sasl_path[MAXPATHLEN];
+
+LDAP *
+slapi_ldap_init_ext(
+ const char *ldapurl, /* full ldap url */
+ const char *hostname, /* can also use this to override
+ host in url */
+ int port, /* can also use this to override port in url */
+ int secure, /* 0 for ldap, 1 for ldaps, 2 for starttls -
+ override proto in url */
+ int shared, /* if true, LDAP* will be shared among multiple threads */
+ const char *filename /* for ldapi */
+)
+{
+ LDAPURLDesc *ludp = NULL;
+ LDAP *ld = NULL;
+ int rc = 0;
+ int secureurl = 0;
+
+ /* We need to provide a sasl path used for client connections, especially
+ if the server is not set up to be a sasl server - since mozldap provides
+ no way to override the default path programatically, we set the sasl
+ path to the environment variable SASL_PATH. */
+ char *configpluginpath = config_get_saslpath();
+ char *pluginpath = configpluginpath;
+ char *pp = NULL;
+
+ if (NULL == pluginpath || (*pluginpath == '\0')) {
+ slapi_log_error(SLAPI_LOG_SHELL, "slapi_ldap_init_ext",
+ "configpluginpath == NULL\n");
+ if (!(pluginpath = getenv("SASL_PATH"))) {
+#if defined(LINUX) && defined(__LP64__)
+ pluginpath = "/usr/lib64/sasl2";
+#else
+ pluginpath = "/usr/lib/sasl2";
+#endif
+ }
+ }
+ if ('\0' == util_sasl_path[0] || /* first time */
+ NULL == (pp = strchr(util_sasl_path, '=')) || /* invalid arg for putenv */
+ (0 != strcmp(++pp, pluginpath)) /* sasl_path has been updated */ ) {
+ PR_snprintf(util_sasl_path, sizeof(util_sasl_path),
+ "SASL_PATH=%s", pluginpath);
+ slapi_log_error(SLAPI_LOG_SHELL, "slapi_ldap_init_ext",
+ "putenv(%s)\n", util_sasl_path);
+ putenv(util_sasl_path);
+ }
+ slapi_ch_free_string(&configpluginpath);
+
+ /* if ldapurl is given, parse it */
+ if (ldapurl && ((rc = slapi_ldap_url_parse(ldapurl, &ludp, 0, &secureurl)) ||
+ !ludp)) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_init_ext",
+ "Could not parse given LDAP URL [%s] : error [%s]\n",
+ ldapurl ? ldapurl : "NULL",
+ slapi_urlparse_err2string(rc));
+ goto done;
+ }
+
+ /* use url host if no host given */
+ if (!hostname && ludp && ludp->lud_host) {
+ hostname = ludp->lud_host;
+ }
+
+ /* use url port if no port given */
+ if (!port && ludp && ludp->lud_port) {
+ port = ludp->lud_port;
+ }
+
+ /* use secure setting from url if none given */
+ if (!secure && ludp) {
+ if (secureurl) {
+ secure = 1;
+ } else if (0/* starttls option - not supported yet in LDAP URLs */) {
+ secure = 2;
+ }
+ }
+
+ /* ldap_url_parse doesn't yet handle ldapi */
+ /*
+ if (!filename && ludp && ludp->lud_file) {
+ filename = ludp->lud_file;
+ }
+ */
+
+#ifdef MEMPOOL_EXPERIMENTAL
+ {
+ /*
+ * slapi_ch_malloc functions need to be set to LDAP C SDK
+ */
+ struct ldap_memalloc_fns memalloc_fns;
+ memalloc_fns.ldapmem_malloc = (LDAP_MALLOC_CALLBACK *)slapi_ch_malloc;
+ memalloc_fns.ldapmem_calloc = (LDAP_CALLOC_CALLBACK *)slapi_ch_calloc;
+ memalloc_fns.ldapmem_realloc = (LDAP_REALLOC_CALLBACK *)slapi_ch_realloc;
+ memalloc_fns.ldapmem_free = (LDAP_FREE_CALLBACK *)_free_wrapper;
+ }
+ /*
+ * MEMPOOL_EXPERIMENTAL:
+ * These LDAP C SDK init function needs to be revisited.
+ * In ldap_init called via ldapssl_init and prldap_init initializes
+ * options and set default values including memalloc_fns, then it
+ * initializes as sasl client by calling sasl_client_init. In
+ * sasl_client_init, it creates mechlist using the malloc function
+ * available at the moment which could mismatch the malloc/free functions
+ * set later.
+ */
+#endif
+
+#if defined(USE_OPENLDAP)
+ if (ldapurl) {
+ rc = ldap_initialize(&ld, ldapurl);
+ if (rc) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_init_ext",
+ "Could not initialize LDAP connection to [%s]: %d:%s\n",
+ ldapurl, rc, ldap_err2string(rc));
+ goto done;
+ }
+ } else {
+ char *makeurl = NULL;
+ if (filename) {
+ makeurl = slapi_ch_smprintf("ldapi://%s/", filename);
+ } else { /* host port */
+ makeurl = slapi_ch_smprintf("ldap%s://%s:%d/", (secure == 1 ? "s" : ""), hostname, port);
+ }
+ rc = ldap_initialize(&ld, makeurl);
+ if (rc) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_init_ext",
+ "Could not initialize LDAP connection to [%s]: %d:%s\n",
+ makeurl, rc, ldap_err2string(rc));
+ slapi_ch_free_string(&makeurl);
+ goto done;
+ }
+ slapi_ch_free_string(&makeurl);
+ }
+#else /* !USE_OPENLDAP */
+ if (filename) {
+ /* ldapi in mozldap client is not yet supported */
+ } else if (secure == 1) {
+ ld = ldapssl_init(hostname, port, secure);
+ } else { /* regular ldap and/or starttls */
+ /*
+ * Leverage the libprldap layer to take care of all the NSPR
+ * integration.
+ * Note that ldapssl_init() uses libprldap implicitly.
+ */
+ ld = prldap_init(hostname, port, shared);
+ }
+#endif /* !USE_OPENLDAP */
+ /* Update snmp interaction table */
+ if (hostname) {
+ if (ld == NULL) {
+ set_snmp_interaction_row((char *)hostname, port, -1);
+ } else {
+ set_snmp_interaction_row((char *)hostname, port, 0);
+ }
+ }
+
+ if ((ld != NULL) && !filename) {
+ /*
+ * Set the outbound LDAP I/O timeout based on the server config.
+ */
+ int io_timeout_ms = config_get_outbound_ldap_io_timeout();
+ if (io_timeout_ms > 0) {
+#if defined(USE_OPENLDAP)
+ struct timeval tv;
+ tv.tv_sec = io_timeout_ms / 1000;
+ tv.tv_usec = (io_timeout_ms % 1000) * 1000;
+ if (LDAP_OPT_SUCCESS != ldap_set_option(ld, LDAP_OPT_NETWORK_TIMEOUT, &tv)) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_init_ext",
+ "failed: unable to set outbound I/O "
+ "timeout to %dms\n",
+ io_timeout_ms);
+ slapi_ldap_unbind(ld);
+ ld = NULL;
+ goto done;
+ }
+#else /* !USE_OPENLDAP */
+ if (prldap_set_session_option(ld, NULL, PRLDAP_OPT_IO_MAX_TIMEOUT,
+ io_timeout_ms) != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_init_ext",
+ "failed: unable to set outbound I/O "
+ "timeout to %dms\n",
+ io_timeout_ms);
+ slapi_ldap_unbind(ld);
+ ld = NULL;
+ goto done;
+ }
+#endif /* !USE_OPENLDAP */
+ }
+
+ /*
+ * Set SSL strength (server certificate validity checking).
+ */
+ if (secure > 0) {
+#if !defined(USE_OPENLDAP)
+ int ssl_strength = 0;
+#endif
+ LDAP *myld = NULL;
+
+ /* we can only use the set functions below with a real
+ LDAP* if it has already gone through ldapssl_init -
+ so, use NULL if using starttls */
+ if (secure == 1) {
+ myld = ld;
+ }
+
+ if (config_get_ssl_check_hostname()) {
+ /* check hostname against name in certificate */
+#if defined(USE_OPENLDAP)
+ if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, "hard"))) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_init_ext",
+ "failed: unable to set REQUIRE_CERT option to hard\n");
+ }
+#else /* !USE_OPENLDAP */
+ ssl_strength = LDAPSSL_AUTH_CNCHECK;
+#endif /* !USE_OPENLDAP */
+ } else {
+ /* verify certificate only */
+#if defined(USE_OPENLDAP)
+ if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, "allow"))) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_init_ext",
+ "failed: unable to set REQUIRE_CERT option to allow\n");
+ }
+#else /* !USE_OPENLDAP */
+ ssl_strength = LDAPSSL_AUTH_CERT;
+#endif /* !USE_OPENLDAP */
+ }
+
+#if defined(USE_OPENLDAP)
+#if defined(LDAP_OPT_X_TLS_PROTOCOL_MIN)
+ if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_PROTOCOL_MIN, LDAP_OPT_X_TLS_PROTOCOL_SSL3))) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_init_ext",
+ "failed: unable to set minimum TLS protocol level to SSL3n");
+ }
+#endif /* LDAP_OPT_X_TLS_PROTOCOL_MIN */
+#else /* !USE_OPENLDAP */
+ if ((rc = ldapssl_set_strength(myld, ssl_strength)) ||
+ (rc = ldapssl_set_option(myld, SSL_ENABLE_SSL2, PR_FALSE)) ||
+ (rc = ldapssl_set_option(myld, SSL_ENABLE_SSL3, PR_TRUE)) ||
+ (rc = ldapssl_set_option(myld, SSL_ENABLE_TLS, PR_TRUE))) {
+ int prerr = PR_GetError();
+
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_init_ext",
+ "failed: unable to set SSL options ("
+ SLAPI_COMPONENT_NAME_NSPR " error %d - %s)",
+ prerr, slapd_pr_strerror(prerr));
+
+ }
+ if (secure == 1) {
+ /* tell bind code we are using SSL */
+ ldap_set_option(ld, LDAP_OPT_SSL, LDAP_OPT_ON);
+ }
+#endif /* !USE_OPENLDAP */
+ }
+ }
+
+ if (ld && (secure == 2)) {
+ /* We don't have a way to stash context data with the LDAP*, so we
+ stash the information in the client controls (currently unused).
+ We don't want to open the connection in ldap_init, since that's
+ not the semantic - the connection is not usually opened until
+ the first operation is sent, which is usually the bind - or
+ in this case, the start_tls - so we stash the start_tls so
+ we can do it in slapi_ldap_bind - note that this will get
+ cleaned up when the LDAP* is disposed of
+ */
+ LDAPControl start_tls_dummy_ctrl;
+ LDAPControl **clientctrls = NULL;
+
+ /* returns copy of controls */
+ ldap_get_option(ld, LDAP_OPT_CLIENT_CONTROLS, &clientctrls);
+
+ start_tls_dummy_ctrl.ldctl_oid = START_TLS_OID;
+ start_tls_dummy_ctrl.ldctl_value.bv_val = NULL;
+ start_tls_dummy_ctrl.ldctl_value.bv_len = 0;
+ start_tls_dummy_ctrl.ldctl_iscritical = 0;
+ slapi_add_control_ext(&clientctrls, &start_tls_dummy_ctrl, 1);
+ /* set option frees old list and copies the new list */
+ ldap_set_option(ld, LDAP_OPT_CLIENT_CONTROLS, clientctrls);
+ ldap_controls_free(clientctrls); /* free the copy */
+ }
+
+ slapi_log_error(SLAPI_LOG_SHELL, "slapi_ldap_init_ext",
+ "Success: set up conn to [%s:%d]%s\n",
+ hostname, port,
+ (secure == 2) ? " using startTLS" :
+ ((secure == 1) ? " using SSL" : ""));
+done:
+ ldap_free_urldesc(ludp);
+
+ return( ld );
+}
+
+/*
+ * Function: slapi_ldap_init()
+ * Description: just like ldap_ssl_init() but also arranges for the LDAP
+ * session handle returned to be safely shareable by multiple threads
+ * if "shared" is non-zero.
+ * Returns:
+ * an LDAP session handle (NULL if some local error occurs).
+ */
+LDAP *
+slapi_ldap_init( char *ldaphost, int ldapport, int secure, int shared )
+{
+ return slapi_ldap_init_ext(NULL, ldaphost, ldapport, secure, shared, NULL);
+}
+
+/*
+ * Does the correct bind operation simple/sasl/cert depending
+ * on the arguments passed in. If the user specified to use
+ * starttls in init, this will do the starttls first. If using
+ * ssl or client cert auth, this will initialize the client side
+ * of that.
+ */
+int
+slapi_ldap_bind(
+ LDAP *ld, /* ldap connection */
+ const char *bindid, /* usually a bind DN for simple bind */
+ const char *creds, /* usually a password for simple bind */
+ const char *mech, /* name of mechanism */
+ LDAPControl **serverctrls, /* additional controls to send */
+ LDAPControl ***returnedctrls, /* returned controls */
+ struct timeval *timeout, /* timeout */
+ int *msgidp /* pass in non-NULL for async handling */
+)
+{
+ int rc = LDAP_SUCCESS;
+ LDAPControl **clientctrls = NULL;
+ int secure = 0;
+ struct berval bvcreds = {0, NULL};
+ LDAPMessage *result = NULL;
+ struct berval *servercredp = NULL;
+
+ /* do starttls if requested
+ NOTE - starttls is an extop, not a control, but we don't have
+ a place we can stash this information in the LDAP*, other
+ than the currently unused clientctrls */
+ ldap_get_option(ld, LDAP_OPT_CLIENT_CONTROLS, &clientctrls);
+ if (clientctrls && clientctrls[0] &&
+ slapi_control_present(clientctrls, START_TLS_OID, NULL, NULL)) {
+ secure = 2;
+ } else {
+#if defined(USE_OPENLDAP)
+ /* openldap doesn't have a SSL/TLS yes/no flag - so grab the
+ ldapurl, parse it, and see if it is a secure one */
+ char *ldapurl = NULL;
+ LDAPURLDesc *ludp = NULL;
+
+ ldap_get_option(ld, LDAP_OPT_URI, &ldapurl);
+ slapi_ldap_url_parse(ldapurl, &ludp, 0, &secure);
+ ldap_free_urldesc(ludp);
+ slapi_ch_free_string(&ldapurl);
+#else /* !USE_OPENLDAP */
+ ldap_get_option(ld, LDAP_OPT_SSL, &secure);
+#endif
+ }
+ ldap_controls_free(clientctrls);
+ ldap_set_option(ld, LDAP_OPT_CLIENT_CONTROLS, NULL);
+
+ if ((secure > 0) && mech && !strcmp(mech, LDAP_SASL_EXTERNAL)) {
+ /* SSL connections will use the server's security context
+ and cert for client auth */
+ rc = slapd_SSL_client_auth(ld);
+
+ if (rc != 0) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_bind",
+ "Error: could not configure the server for cert "
+ "auth - error %d - make sure the server is "
+ "correctly configured for SSL/TLS\n", rc);
+ goto done;
+ } else {
+ slapi_log_error(SLAPI_LOG_SHELL, "slapi_ldap_bind",
+ "Set up conn to use client auth\n");
+ }
+ bvcreds.bv_val = NULL; /* ignore username and passed in creds */
+ bvcreds.bv_len = 0; /* for external auth */
+ bindid = NULL;
+ } else { /* other type of auth */
+ bvcreds.bv_val = (char *)creds;
+ bvcreds.bv_len = creds ? strlen(creds) : 0;
+ }
+
+ if (secure == 2) { /* send start tls */
+ rc = ldap_start_tls_s(ld, NULL /* serverctrls?? */, NULL);
+ if (LDAP_SUCCESS != rc) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_bind",
+ "Error: could not send startTLS request: "
+ "error %d (%s)\n",
+ rc, ldap_err2string(rc));
+ goto done;
+ }
+ slapi_log_error(SLAPI_LOG_SHELL, "slapi_ldap_bind",
+ "startTLS started on connection\n");
+ }
+
+ /* The connection has been set up - now do the actual bind, depending on
+ the mechanism and arguments */
+ if (!mech || (mech == LDAP_SASL_SIMPLE) ||
+ !strcmp(mech, LDAP_SASL_EXTERNAL)) {
+ int mymsgid = 0;
+
+ slapi_log_error(SLAPI_LOG_SHELL, "slapi_ldap_bind",
+ "attempting %s bind with id [%s] creds [%s]\n",
+ mech ? mech : "SIMPLE",
+ bindid, creds);
+ if ((rc = ldap_sasl_bind(ld, bindid, mech, &bvcreds, serverctrls,
+ NULL /* clientctrls */, &mymsgid))) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_bind",
+ "Error: could not send bind request for id "
+ "[%s] mech [%s]: error %d (%s) %d (%s) %d (%s)\n",
+ bindid ? bindid : "(anon)",
+ mech ? mech : "SIMPLE",
+ rc, ldap_err2string(rc),
+ PR_GetError(), slapd_pr_strerror(PR_GetError()),
+ errno, slapd_system_strerror(errno));
+ goto done;
+ }
+
+ if (msgidp) { /* let caller process result */
+ *msgidp = mymsgid;
+ } else { /* process results */
+ rc = ldap_result(ld, mymsgid, LDAP_MSG_ALL, timeout, &result);
+ if (-1 == rc) { /* error */
+ rc = slapi_ldap_get_lderrno(ld, NULL, NULL);
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_bind",
+ "Error reading bind response for id "
+ "[%s] mech [%s]: error %d (%s)\n",
+ bindid ? bindid : "(anon)",
+ mech ? mech : "SIMPLE",
+ rc, ldap_err2string(rc));
+ goto done;
+ } else if (rc == 0) { /* timeout */
+ rc = LDAP_TIMEOUT;
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_bind",
+ "Error: timeout after [%ld.%ld] seconds reading "
+ "bind response for [%s] mech [%s]\n",
+ timeout ? timeout->tv_sec : 0,
+ timeout ? timeout->tv_usec : 0,
+ bindid ? bindid : "(anon)",
+ mech ? mech : "SIMPLE");
+ goto done;
+ }
+ /* if we got here, we were able to read success result */
+ /* Get the controls sent by the server if requested */
+ if (returnedctrls) {
+ if ((rc = ldap_parse_result(ld, result, &rc, NULL, NULL,
+ NULL, returnedctrls,
+ 0)) != LDAP_SUCCESS) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_bind",
+ "Error: could not bind id "
+ "[%s] mech [%s]: error %d (%s)\n",
+ bindid ? bindid : "(anon)",
+ mech ? mech : "SIMPLE",
+ rc, ldap_err2string(rc));
+ goto done;
+ }
+ }
+
+ /* parse the bind result and get the ldap error code */
+ if ((rc = ldap_parse_sasl_bind_result(ld, result, &servercredp,
+ 0))) {
+ rc = slapi_ldap_get_lderrno(ld, NULL, NULL);
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_bind",
+ "Error: could not read bind results for id "
+ "[%s] mech [%s]: error %d (%s)\n",
+ bindid ? bindid : "(anon)",
+ mech ? mech : "SIMPLE",
+ rc, ldap_err2string(rc));
+ goto done;
+ }
+ }
+ } else {
+ /* a SASL mech - set the sasl ssf to 0 if using TLS/SSL */
+ /* openldap supports tls + sasl security */
+#if !defined(USE_OPENLDAP)
+ if (secure) {
+ sasl_ssf_t max_ssf = 0;
+ ldap_set_option(ld, LDAP_OPT_X_SASL_SSF_MAX, &max_ssf);
+ }
+#endif
+ rc = slapd_ldap_sasl_interactive_bind(ld, bindid, creds, mech,
+ serverctrls, returnedctrls,
+ msgidp);
+ if (LDAP_SUCCESS != rc) {
+ slapi_log_error(SLAPI_LOG_FATAL, "slapi_ldap_bind",
+ "Error: could not perform interactive bind for id "
+ "[%s] mech [%s]: error %d (%s)\n",
+ bindid ? bindid : "(anon)",
+ mech ? mech : "SIMPLE",
+ rc, ldap_err2string(rc));
+ }
+ }
+
+done:
+ slapi_ch_bvfree(&servercredp);
+ ldap_msgfree(result);
+
+ return rc;
+}
+
+/* the following implements the client side of sasl bind, for LDAP server
+ -> LDAP server SASL */
+
+typedef struct {
+ char *mech;
+ char *authid;
+ char *username;
+ char *passwd;
+ char *realm;
+} ldapSaslInteractVals;
+
+#ifdef HAVE_KRB5
+static void set_krb5_creds(
+ const char *authid,
+ const char *username,
+ const char *passwd,
+ const char *realm,
+ ldapSaslInteractVals *vals
+);
+#endif
+
+static void *
+ldap_sasl_set_interact_vals(LDAP *ld, const char *mech, const char *authid,
+ const char *username, const char *passwd,
+ const char *realm)
+{
+ ldapSaslInteractVals *vals = NULL;
+ char *idprefix = "";
+
+ vals = (ldapSaslInteractVals *)
+ slapi_ch_calloc(1, sizeof(ldapSaslInteractVals));
+
+ if (!vals) {
+ return NULL;
+ }
+
+ if (mech) {
+ vals->mech = slapi_ch_strdup(mech);
+ } else {
+ ldap_get_option(ld, LDAP_OPT_X_SASL_MECH, &vals->mech);
+ }
+
+ if (vals->mech && !strcasecmp(vals->mech, "DIGEST-MD5")) {
+ idprefix = "dn:"; /* prefix name and id with this string */
+ }
+
+ if (authid) { /* use explicit passed in value */
+ vals->authid = slapi_ch_smprintf("%s%s", idprefix, authid);
+ } else { /* use option value if any */
+ ldap_get_option(ld, LDAP_OPT_X_SASL_AUTHCID, &vals->authid);
+ if (!vals->authid) {
+/* get server user id? */
+ vals->authid = slapi_ch_strdup("");
+ }
+ }
+
+ if (username) { /* use explicit passed in value */
+ vals->username = slapi_ch_smprintf("%s%s", idprefix, username);
+ } else { /* use option value if any */
+ ldap_get_option(ld, LDAP_OPT_X_SASL_AUTHZID, &vals->username);
+ if (!vals->username) { /* use default sasl value */
+ vals->username = slapi_ch_strdup("");
+ }
+ }
+
+ if (passwd) {
+ vals->passwd = slapi_ch_strdup(passwd);
+ } else {
+ vals->passwd = slapi_ch_strdup("");
+ }
+
+ if (realm) {
+ vals->realm = slapi_ch_strdup(realm);
+ } else {
+ ldap_get_option(ld, LDAP_OPT_X_SASL_REALM, &vals->realm);
+ if (!vals->realm) { /* use default sasl value */
+ vals->realm = slapi_ch_strdup("");
+ }
+ }
+
+#ifdef HAVE_KRB5
+ if (mech && !strcmp(mech, "GSSAPI")) {
+ set_krb5_creds(authid, username, passwd, realm, vals);
+ }
+#endif /* HAVE_KRB5 */
+
+ return vals;
+}
+
+static void
+ldap_sasl_free_interact_vals(void *defaults)
+{
+ ldapSaslInteractVals *vals = defaults;
+
+ if (vals) {
+ slapi_ch_free_string(&vals->mech);
+ slapi_ch_free_string(&vals->authid);
+ slapi_ch_free_string(&vals->username);
+ slapi_ch_free_string(&vals->passwd);
+ slapi_ch_free_string(&vals->realm);
+ slapi_ch_free(&defaults);
+ }
+}
+
+static int
+ldap_sasl_get_val(ldapSaslInteractVals *vals, sasl_interact_t *interact, unsigned flags)
+{
+ const char *defvalue = interact->defresult;
+ int authtracelevel = SLAPI_LOG_SHELL; /* special auth tracing */
+
+ if (vals != NULL) {
+ switch(interact->id) {
+ case SASL_CB_AUTHNAME:
+ defvalue = vals->authid;
+ slapi_log_error(authtracelevel, "ldap_sasl_get_val",
+ "Using value [%s] for SASL_CB_AUTHNAME\n",
+ defvalue ? defvalue : "(null)");
+ break;
+ case SASL_CB_USER:
+ defvalue = vals->username;
+ slapi_log_error(authtracelevel, "ldap_sasl_get_val",
+ "Using value [%s] for SASL_CB_USER\n",
+ defvalue ? defvalue : "(null)");
+ break;
+ case SASL_CB_PASS:
+ defvalue = vals->passwd;
+ slapi_log_error(authtracelevel, "ldap_sasl_get_val",
+ "Using value [%s] for SASL_CB_PASS\n",
+ defvalue ? defvalue : "(null)");
+ break;
+ case SASL_CB_GETREALM:
+ defvalue = vals->realm;
+ slapi_log_error(authtracelevel, "ldap_sasl_get_val",
+ "Using value [%s] for SASL_CB_GETREALM\n",
+ defvalue ? defvalue : "(null)");
+ break;
+ }
+ }
+
+ if (defvalue != NULL) {
+ interact->result = defvalue;
+ if ((char *)interact->result == NULL)
+ return (LDAP_NO_MEMORY);
+ interact->len = strlen((char *)(interact->result));
+ }
+ return (LDAP_SUCCESS);
+}
+
+static int
+ldap_sasl_interact_cb(LDAP *ld, unsigned flags, void *defaults, void *prompts)
+{
+ sasl_interact_t *interact = NULL;
+ ldapSaslInteractVals *sasldefaults = defaults;
+ int rc;
+
+ if (prompts == NULL) {
+ return (LDAP_PARAM_ERROR);
+ }
+
+ for (interact = prompts; interact->id != SASL_CB_LIST_END; interact++) {
+ /* Obtain the default value */
+ if ((rc = ldap_sasl_get_val(sasldefaults, interact, flags)) != LDAP_SUCCESS) {
+ return (rc);
+ }
+ }
+
+ return (LDAP_SUCCESS);
+}
+
+/* figure out from the context and this error if we should
+ attempt to retry the bind */
+static int
+can_retry_bind(LDAP *ld, const char *mech, const char *bindid,
+ const char *creds, int rc, const char *errmsg)
+{
+ int localrc = 0;
+ if (errmsg && strstr(errmsg, "Ticket expired")) {
+ localrc = 1;
+ }
+
+ return localrc;
+}
+
+int
+slapd_ldap_sasl_interactive_bind(
+ LDAP *ld, /* ldap connection */
+ const char *bindid, /* usually a bind DN for simple bind */
+ const char *creds, /* usually a password for simple bind */
+ const char *mech, /* name of mechanism */
+ LDAPControl **serverctrls, /* additional controls to send */
+ LDAPControl ***returnedctrls, /* returned controls */
+ int *msgidp /* pass in non-NULL for async handling */
+)
+{
+ int rc = LDAP_SUCCESS;
+ int tries = 0;
+
+ while (tries < 2) {
+ void *defaults = ldap_sasl_set_interact_vals(ld, mech, bindid, bindid,
+ creds, NULL);
+ /* have to first set the defaults used by the callback function */
+ /* call the bind function */
+ /* openldap does not have the ext version - not sure how to get the
+ returned controls */
+#if defined(USE_OPENLDAP)
+ rc = ldap_sasl_interactive_bind_s(ld, bindid, mech, serverctrls,
+ NULL, LDAP_SASL_QUIET,
+ ldap_sasl_interact_cb, defaults);
+#else
+ rc = ldap_sasl_interactive_bind_ext_s(ld, bindid, mech, serverctrls,
+ NULL, LDAP_SASL_QUIET,
+ ldap_sasl_interact_cb, defaults,
+ returnedctrls);
+#endif
+ ldap_sasl_free_interact_vals(defaults);
+ if (LDAP_SUCCESS != rc) {
+ char *errmsg = NULL;
+ rc = slapi_ldap_get_lderrno(ld, NULL, &errmsg);
+ slapi_log_error(SLAPI_LOG_FATAL, "slapd_ldap_sasl_interactive_bind",
+ "Error: could not perform interactive bind for id "
+ "[%s] mech [%s]: error %d (%s) (%s)\n",
+ bindid ? bindid : "(anon)",
+ mech ? mech : "SIMPLE",
+ rc, ldap_err2string(rc), errmsg);
+ if (can_retry_bind(ld, mech, bindid, creds, rc, errmsg)) {
+ ; /* pass through to retry one time */
+ } else {
+ break; /* done - fail - cannot retry */
+ }
+ } else {
+ break; /* done - success */
+ }
+ tries++;
+ }
+
+ return rc;
+}
+
+#ifdef HAVE_KRB5
+#include <krb5.h>
+
+/* for some reason this is not in the public API?
+ but it is documented e.g. man kinit */
+#ifndef KRB5_ENV_CCNAME
+#define KRB5_ENV_CCNAME "KRB5CCNAME"
+#endif
+
+static void
+show_one_credential(int authtracelevel,
+ krb5_context ctx, krb5_creds *cred)
+{
+ char *logname = "show_one_credential";
+ krb5_error_code rc;
+ char *name = NULL, *sname = NULL;
+ char startts[BUFSIZ], endts[BUFSIZ], renewts[BUFSIZ];
+
+ if ((rc = krb5_unparse_name(ctx, cred->client, &name))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not get client name from credential: %d (%s)\n",
+ rc, error_message(rc));
+ goto cleanup;
+ }
+ if ((rc = krb5_unparse_name(ctx, cred->server, &sname))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not get server name from credential: %d (%s)\n",
+ rc, error_message(rc));
+ goto cleanup;
+ }
+ if (!cred->times.starttime) {
+ cred->times.starttime = cred->times.authtime;
+ }
+ krb5_timestamp_to_sfstring((krb5_timestamp)cred->times.starttime,
+ startts, sizeof(startts), NULL);
+ krb5_timestamp_to_sfstring((krb5_timestamp)cred->times.endtime,
+ endts, sizeof(endts), NULL);
+ krb5_timestamp_to_sfstring((krb5_timestamp)cred->times.renew_till,
+ renewts, sizeof(renewts), NULL);
+
+ slapi_log_error(authtracelevel, logname,
+ "\tKerberos credential: client [%s] server [%s] "
+ "start time [%s] end time [%s] renew time [%s] "
+ "flags [0x%x]\n", name, sname, startts, endts,
+ renewts, (uint32_t)cred->ticket_flags);
+
+cleanup:
+ krb5_free_unparsed_name(ctx, name);
+ krb5_free_unparsed_name(ctx, sname);
+
+ return;
+}
+
+/*
+ * Call this after storing the credentials in the cache
+ */
+static void
+show_cached_credentials(int authtracelevel,
+ krb5_context ctx, krb5_ccache cc,
+ krb5_principal princ)
+{
+ char *logname = "show_cached_credentials";
+ krb5_error_code rc = 0;
+ krb5_creds creds;
+ krb5_cc_cursor cur;
+ char *princ_name = NULL;
+
+ if ((rc = krb5_unparse_name(ctx, princ, &princ_name))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not get principal name from principal: %d (%s)\n",
+ rc, error_message(rc));
+ goto cleanup;
+ }
+
+ slapi_log_error(authtracelevel, logname,
+ "Ticket cache: %s:%s\nDefault principal: %s\n\n",
+ krb5_cc_get_type(ctx, cc),
+ krb5_cc_get_name(ctx, cc), princ_name);
+
+ if ((rc = krb5_cc_start_seq_get(ctx, cc, &cur))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not get cursor to iterate cached credentials: "
+ "%d (%s)\n", rc, error_message(rc));
+ goto cleanup;
+ }
+
+ while (!(rc = krb5_cc_next_cred(ctx, cc, &cur, &creds))) {
+ show_one_credential(authtracelevel, ctx, &creds);
+ krb5_free_cred_contents(ctx, &creds);
+ }
+ if (rc == KRB5_CC_END) {
+ if ((rc = krb5_cc_end_seq_get(ctx, cc, &cur))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not close cached credentials cursor: "
+ "%d (%s)\n", rc, error_message(rc));
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ krb5_free_unparsed_name(ctx, princ_name);
+
+ return;
+}
+
+static int
+looks_like_a_dn(const char *username)
+{
+ return (username && strchr(username, '='));
+}
+
+static int
+credentials_are_valid(
+ krb5_context ctx,
+ krb5_ccache cc,
+ krb5_principal princ,
+ const char *princ_name,
+ int *rc
+)
+{
+ char *logname = "credentials_are_valid";
+ int myrc = 0;
+ krb5_creds mcreds; /* match these values */
+ krb5_creds creds; /* returned creds */
+ char *tgs_princ_name = NULL;
+ krb5_timestamp currenttime;
+ int authtracelevel = SLAPI_LOG_SHELL; /* special auth tracing */
+ int realm_len;
+ char *realm_str;
+ int time_buffer = 30; /* seconds - go ahead and renew if creds are
+ about to expire */
+
+ memset(&mcreds, 0, sizeof(mcreds));
+ memset(&creds, 0, sizeof(creds));
+ *rc = 0;
+ if (!cc) {
+ /* ok - no error */
+ goto cleanup;
+ }
+
+ /* have to construct the tgs server principal in
+ order to set mcreds.server required in order
+ to use krb5_cc_retrieve_creds() */
+ /* get default realm first */
+ realm_len = krb5_princ_realm(ctx, princ)->length;
+ realm_str = krb5_princ_realm(ctx, princ)->data;
+ tgs_princ_name = slapi_ch_smprintf("%s/%*s@%*s", KRB5_TGS_NAME,
+ realm_len, realm_str,
+ realm_len, realm_str);
+
+ if ((*rc = krb5_parse_name(ctx, tgs_princ_name, &mcreds.server))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could parse principal [%s]: %d (%s)\n",
+ tgs_princ_name, *rc, error_message(*rc));
+ goto cleanup;
+ }
+
+ mcreds.client = princ;
+ if ((*rc = krb5_cc_retrieve_cred(ctx, cc, 0, &mcreds, &creds))) {
+ if (*rc == KRB5_CC_NOTFOUND) {
+ /* ok - no creds for this princ in the cache */
+ *rc = 0;
+ }
+ goto cleanup;
+ }
+
+ /* have the creds - now look at the timestamp */
+ if ((*rc = krb5_timeofday(ctx, &currenttime))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not get current time: %d (%s)\n",
+ *rc, error_message(*rc));
+ goto cleanup;
+ }
+
+ if (currenttime > (creds.times.endtime + time_buffer)) {
+ slapi_log_error(authtracelevel, logname,
+ "Credentials for [%s] have expired or will soon "
+ "expire - now [%d] endtime [%d]\n", princ_name,
+ currenttime, creds.times.endtime);
+ goto cleanup;
+ }
+
+ myrc = 1; /* credentials are valid */
+cleanup:
+ krb5_free_cred_contents(ctx, &creds);
+ slapi_ch_free_string(&tgs_princ_name);
+ if (mcreds.server) {
+ krb5_free_principal(ctx, mcreds.server);
+ }
+
+ return myrc;
+}
+
+/*
+ * This implementation assumes that we want to use the
+ * keytab from the default keytab env. var KRB5_KTNAME
+ * as. This code is very similar to kinit -k -t. We
+ * get a krb context, get the default keytab, get
+ * the credentials from the keytab, authenticate with
+ * those credentials, create a ccache, store the
+ * credentials in the ccache, and set the ccache
+ * env var to point to those credentials.
+ */
+static void
+set_krb5_creds(
+ const char *authid,
+ const char *username,
+ const char *passwd,
+ const char *realm,
+ ldapSaslInteractVals *vals
+)
+{
+ char *logname = "set_krb5_creds";
+ const char *cc_type = "MEMORY"; /* keep cred cache in memory */
+ krb5_context ctx = NULL;
+ krb5_ccache cc = NULL;
+ krb5_principal princ = NULL;
+ char *princ_name = NULL;
+ krb5_error_code rc = 0;
+ krb5_creds creds;
+ krb5_keytab kt = NULL;
+ char *cc_name = NULL;
+ char ktname[MAX_KEYTAB_NAME_LEN];
+ static char cc_env_name[1024+32]; /* size from ccdefname.c */
+ int new_ccache = 0;
+ int authtracelevel = SLAPI_LOG_SHELL; /* special auth tracing
+ not sure what shell was
+ used for, does not
+ appear to be used
+ currently */
+
+ /* probably have to put a mutex around this whole thing, to avoid
+ problems with reentrancy, since we are setting a "global"
+ variable via an environment variable */
+
+ /* wipe this out so we can safely free it later if we
+ short circuit */
+ memset(&creds, 0, sizeof(creds));
+
+ /* initialize the kerberos context */
+ if ((rc = krb5_init_context(&ctx))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not init Kerberos context: %d (%s)\n",
+ rc, error_message(rc));
+ goto cleanup;
+ }
+
+ /* see if there is already a ccache, and see if there are
+ creds in the ccache */
+ /* grab the default ccache - note: this does not open the cache */
+ if ((rc = krb5_cc_default(ctx, &cc))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not get default Kerberos ccache: %d (%s)\n",
+ rc, error_message(rc));
+ goto cleanup;
+ }
+
+ /* use this cache - construct the full cache name */
+ cc_name = slapi_ch_smprintf("%s:%s", krb5_cc_get_type(ctx, cc),
+ krb5_cc_get_name(ctx, cc));
+
+ /* grab the principal from the ccache - will fail if there
+ is no ccache */
+ if ((rc = krb5_cc_get_principal(ctx, cc, &princ))) {
+ if (KRB5_FCC_NOFILE == rc) { /* no cache - ok */
+ slapi_log_error(authtracelevel, logname,
+ "The default credentials cache [%s] not found: "
+ "will create a new one.\n", cc_name);
+ /* close the cache - we will create a new one below */
+ krb5_cc_close(ctx, cc);
+ cc = NULL;
+ slapi_ch_free_string(&cc_name);
+ /* fall through to the keytab auth code below */
+ } else { /* fatal */
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not open default Kerberos ccache [%s]: "
+ "%d (%s)\n", cc_name, rc, error_message(rc));
+ goto cleanup;
+ }
+ } else { /* have a valid ccache && found principal */
+ if ((rc = krb5_unparse_name(ctx, princ, &princ_name))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Unable to get name of principal from ccache [%s]: "
+ "%d (%s)\n", cc_name, rc, error_message(rc));
+ goto cleanup;
+ }
+ slapi_log_error(authtracelevel, logname,
+ "Using principal [%s] from ccache [%s]\n",
+ princ_name, cc_name);
+ }
+
+ /* if this is not our type of ccache, there is nothing more we can
+ do - just punt and let sasl/gssapi take it's course - this
+ usually means there has been an external kinit e.g. in the
+ start up script, and it is the responsibility of the script to
+ renew those credentials or face lots of sasl/gssapi failures
+ This means, however, that the caller MUST MAKE SURE THERE IS NO
+ DEFAULT CCACHE FILE or the server will attempt to use it (and
+ likely fail) - THERE MUST BE NO DEFAULT CCACHE FILE IF YOU WANT
+ THE SERVER TO AUTHENTICATE WITH THE KEYTAB
+ NOTE: cc types are case sensitive and always upper case */
+ if (cc && strcmp(cc_type, krb5_cc_get_type(ctx, cc))) {
+ static int errmsgcounter = 0;
+ int loglevel = SLAPI_LOG_FATAL;
+ if (errmsgcounter) {
+ loglevel = authtracelevel;
+ }
+ /* make sure we log this message once, in case the user has
+ done something unintended, we want to make sure they know
+ about it. However, if the user knows what he/she is doing,
+ by using an external ccache file, they probably don't want
+ to be notified with an error every time. */
+ slapi_log_error(loglevel, logname,
+ "The server will use the external SASL/GSSAPI "
+ "credentials cache [%s:%s]. If you want the "
+ "server to automatically authenticate with its "
+ "keytab, you must remove this cache. If you "
+ "did not intend to use this cache, you will likely "
+ "see many SASL/GSSAPI authentication failures.\n",
+ krb5_cc_get_type(ctx, cc), krb5_cc_get_name(ctx, cc));
+ errmsgcounter++;
+ goto cleanup;
+ }
+
+ /* need to figure out which principal to use
+ 1) use the one from the ccache
+ 2) use username
+ 3) construct one in the form ldap/fqdn@REALM
+ */
+ if (!princ && username && !looks_like_a_dn(username) &&
+ (rc = krb5_parse_name(ctx, username, &princ))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Error: could not convert [%s] into a kerberos "
+ "principal: %d (%s)\n", username,
+ rc, error_message(rc));
+ goto cleanup;
+ }
+
+ /* if still no principal, construct one */
+ if (!princ &&
+ (rc = krb5_sname_to_principal(ctx, NULL, "ldap",
+ KRB5_NT_SRV_HST, &princ))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Error: could not construct ldap service "
+ "principal: %d (%s)\n", rc, error_message(rc));
+ goto cleanup;
+ }
+
+ if ((rc = krb5_unparse_name(ctx, princ, &princ_name))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Unable to get name of principal: "
+ "%d (%s)\n", rc, error_message(rc));
+ goto cleanup;
+ }
+
+ slapi_log_error(authtracelevel, logname,
+ "Using principal named [%s]\n", princ_name);
+
+ /* grab the credentials from the ccache, if any -
+ if the credentials are still valid, we do not have
+ to authenticate again */
+ if (credentials_are_valid(ctx, cc, princ, princ_name, &rc)) {
+ slapi_log_error(authtracelevel, logname,
+ "Credentials for principal [%s] are still "
+ "valid - no auth is necessary.\n",
+ princ_name);
+ goto cleanup;
+ } else if (rc) { /* some error other than "there are no credentials" */
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Unable to verify cached credentials for "
+ "principal [%s]: %d (%s)\n", princ_name,
+ rc, error_message(rc));
+ goto cleanup;
+ }
+
+ /* find our default keytab */
+ if ((rc = krb5_kt_default(ctx, &kt))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Unable to get default keytab: %d (%s)\n",
+ rc, error_message(rc));
+ goto cleanup;
+ }
+
+ /* get name of keytab for debugging purposes */
+ if ((rc = krb5_kt_get_name(ctx, kt, ktname, sizeof(ktname)))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Unable to get name of default keytab: %d (%s)\n",
+ rc, error_message(rc));
+ goto cleanup;
+ }
+
+ slapi_log_error(authtracelevel, logname,
+ "Using keytab named [%s]\n", ktname);
+
+ /* now do the actual kerberos authentication using
+ the keytab, and get the creds */
+ rc = krb5_get_init_creds_keytab(ctx, &creds, princ, kt,
+ 0, NULL, NULL);
+ if (rc) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not get initial credentials for principal [%s] "
+ "in keytab [%s]: %d (%s)\n",
+ princ_name, ktname, rc, error_message(rc));
+ goto cleanup;
+ }
+
+ /* completely done with the keytab now, close it */
+ krb5_kt_close(ctx, kt);
+ kt = NULL; /* no double free */
+
+ /* we now have the creds and the principal to which the
+ creds belong - use or allocate a new memory based
+ cache to hold the creds */
+ if (!cc_name) {
+#if HAVE_KRB5_CC_NEW_UNIQUE
+ /* krb5_cc_new_unique is a new convenience function which
+ generates a new unique name and returns a memory
+ cache with that name */
+ if ((rc = krb5_cc_new_unique(ctx, cc_type, NULL, &cc))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not create new unique memory ccache: "
+ "%d (%s)\n",
+ rc, error_message(rc));
+ goto cleanup;
+ }
+ cc_name = slapi_ch_smprintf("%s:%s", cc_type,
+ krb5_cc_get_name(ctx, cc));
+#else
+ /* store the cache in memory - krb5_init_context uses malloc
+ to create the ctx, so the address should be unique enough
+ for our purposes */
+ if (!(cc_name = slapi_ch_smprintf("%s:%p", cc_type, ctx))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could create Kerberos memory ccache: "
+ "out of memory\n");
+ rc = 1;
+ goto cleanup;
+ }
+#endif
+ slapi_log_error(authtracelevel, logname,
+ "Generated new memory ccache [%s]\n", cc_name);
+ new_ccache = 1; /* need to set this in env. */
+ } else {
+ slapi_log_error(authtracelevel, logname,
+ "Using existing ccache [%s]\n", cc_name);
+ }
+
+ /* krb5_cc_resolve is basically like an init -
+ this creates the cache structure, and creates a slot
+ for the cache in the static linked list in memory, if
+ there is not already a slot -
+ see cc_memory.c for details
+ cc could already have been created by new_unique above
+ */
+ if (!cc && (rc = krb5_cc_resolve(ctx, cc_name, &cc))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not create ccache [%s]: %d (%s)\n",
+ cc_name, rc, error_message(rc));
+ goto cleanup;
+ }
+
+ /* wipe out previous contents of cache for this principal, if any */
+ if ((rc = krb5_cc_initialize(ctx, cc, princ))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not initialize ccache [%s] for the new "
+ "credentials for principal [%s]: %d (%s)\n",
+ cc_name, princ_name, rc, error_message(rc));
+ goto cleanup;
+ }
+
+ /* store the credentials in the cache */
+ if ((rc = krb5_cc_store_cred(ctx, cc, &creds))) {
+ slapi_log_error(SLAPI_LOG_FATAL, logname,
+ "Could not store the credentials in the "
+ "ccache [%s] for principal [%s]: %d (%s)\n",
+ cc_name, princ_name, rc, error_message(rc));
+ goto cleanup;
+ }
+
+ /* now, do a "klist" to show the credential information, and log it */
+ show_cached_credentials(authtracelevel, ctx, cc, princ);
+
+ /* set the CC env var to the value of the cc cache name */
+ /* since we can't pass krb5 context up and out of here
+ and down through the ldap sasl layer, we set this
+ env var so that calls to krb5_cc_default_name will
+ use this */
+ if (new_ccache) {
+ PR_snprintf(cc_env_name, sizeof(cc_env_name),
+ "%s=%s", KRB5_ENV_CCNAME, cc_name);
+ PR_SetEnv(cc_env_name);
+ slapi_log_error(authtracelevel, logname,
+ "Set new env for ccache: [%s]\n",
+ cc_env_name);
+ }
+
+cleanup:
+ /* use NULL as username and authid */
+ slapi_ch_free_string(&vals->username);
+ slapi_ch_free_string(&vals->authid);
+
+ krb5_free_unparsed_name(ctx, princ_name);
+ if (kt) { /* NULL not allowed */
+ krb5_kt_close(ctx, kt);
+ }
+ if (creds.client == princ) {
+ creds.client = NULL;
+ }
+ krb5_free_cred_contents(ctx, &creds);
+ slapi_ch_free_string(&cc_name);
+ krb5_free_principal(ctx, princ);
+ if (cc) {
+ krb5_cc_close(ctx, cc);
+ }
+ if (ctx) { /* cannot pass NULL to free context */
+ krb5_free_context(ctx);
+ }
+ return;
+}
+
+#endif /* HAVE_KRB5 */