/* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * lib/kdb/kdb_ldap/kdb_ldap_conn.c * * Copyright (c) 2004-2005, Novell, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The copyright holder's name is not used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "autoconf.h" #if HAVE_UNISTD_H #include #endif #include "ldap_main.h" #include "ldap_service_stash.h" #include #include static krb5_error_code krb5_validate_ldap_context(krb5_context context, krb5_ldap_context *ldap_context) { krb5_error_code st=0; unsigned char *password=NULL; if (ldap_context->bind_dn == NULL) { st = EINVAL; /* Solaris Kerberos: Keep error messages consistent */ krb5_set_error_message(context, st, gettext("LDAP bind dn value missing")); goto err_out; } if (ldap_context->bind_pwd == NULL && ldap_context->service_password_file == NULL) { st = EINVAL; /* Solaris Kerberos: Keep error messages consistent */ krb5_set_error_message(context, st, gettext("LDAP bind password value missing")); goto err_out; } if (ldap_context->bind_pwd == NULL && ldap_context->service_password_file != NULL && ldap_context->service_cert_path == NULL) { if ((st=krb5_ldap_readpassword(context, ldap_context, &password)) != 0) { prepend_err_str(context, gettext("Error reading password from stash: "), st, st); goto err_out; } /* Check if the returned 'password' is actually the path of a certificate */ if (!strncmp("{FILE}", (char *)password, 6)) { /* 'password' format: \0 */ ldap_context->service_cert_path = strdup((char *)password + strlen("{FILE}")); if (ldap_context->service_cert_path == NULL) { st = ENOMEM; krb5_set_error_message(context, st, gettext("Error: memory allocation failed")); goto err_out; } if (password[strlen((char *)password) + 1] == '\0') ldap_context->service_cert_pass = NULL; else { ldap_context->service_cert_pass = strdup((char *)password + strlen((char *)password) + 1); if (ldap_context->service_cert_pass == NULL) { st = ENOMEM; krb5_set_error_message(context, st, gettext("Error: memory allocation failed")); goto err_out; } } free(password); } else { ldap_context->bind_pwd = (char *)password; if (ldap_context->bind_pwd == NULL) { st = EINVAL; krb5_set_error_message(context, st, gettext("Error reading password from stash")); goto err_out; } } } /* NULL password not allowed */ if (ldap_context->bind_pwd != NULL && strlen(ldap_context->bind_pwd) == 0) { st = EINVAL; krb5_set_error_message(context, st, gettext("Service password length is zero")); goto err_out; } err_out: return st; } /* * Internal Functions called by init functions. */ static krb5_error_code krb5_ldap_bind(ldap_context, ldap_server_handle) krb5_ldap_context *ldap_context; krb5_ldap_server_handle *ldap_server_handle; { krb5_error_code st=0; struct berval bv={0, NULL}, *servercreds=NULL; if (ldap_context->service_cert_path != NULL) { /* Certificate based bind (SASL EXTERNAL mechanism) */ st = ldap_sasl_bind_s(ldap_server_handle->ldap_handle, NULL, /* Authenticating dn */ LDAP_SASL_EXTERNAL, /* Method used for authentication */ &bv, NULL, NULL, &servercreds); while (st == LDAP_SASL_BIND_IN_PROGRESS) { st = ldap_sasl_bind_s(ldap_server_handle->ldap_handle, NULL, LDAP_SASL_EXTERNAL, servercreds, NULL, NULL, &servercreds); } } else { /* password based simple bind */ bv.bv_val = ldap_context->bind_pwd; bv.bv_len = strlen(ldap_context->bind_pwd); st = ldap_sasl_bind_s(ldap_server_handle->ldap_handle, ldap_context->bind_dn, LDAP_SASL_SIMPLE, &bv, NULL, NULL, NULL); } return st; } static krb5_error_code krb5_ldap_initialize(ldap_context, server_info) krb5_ldap_context *ldap_context; krb5_ldap_server_info *server_info; { krb5_error_code st=0; krb5_ldap_server_handle *ldap_server_handle=NULL; char *errstr = NULL; ldap_server_handle = calloc(1, sizeof(krb5_ldap_server_handle)); if (ldap_server_handle == NULL) { st = ENOMEM; goto err_out; } else { /* * Solaris Kerbreros: need ldap_handle to be NULL so calls to * ldap_initialize won't leak handles */ ldap_server_handle->ldap_handle = NULL; } if (strncasecmp(server_info->server_name, "ldapi:", 6) == 0) { /* * Solaris Kerberos: ldapi is not supported on Solaris at this time. * return an error. */ if (ldap_context->kcontext) krb5_set_error_message (ldap_context->kcontext, KRB5_KDB_ACCESS_ERROR, gettext("ldapi is not supported")); st = KRB5_KDB_ACCESS_ERROR; goto err_out; } else { /* * Solaris Kerbreros: need to use SSL to protect LDAP simple and * External binds. */ if (ldap_context->root_certificate_file == NULL) { if (ldap_context->kcontext) krb5_set_error_message (ldap_context->kcontext, KRB5_KDB_ACCESS_ERROR, gettext("ldap_cert_path not set, can not create SSL connection")); st = KRB5_KDB_ACCESS_ERROR; goto err_out; } /* setup for SSL */ if ((st = ldapssl_client_init(ldap_context->root_certificate_file, NULL)) < 0) { if (ldap_context->kcontext) krb5_set_error_message (ldap_context->kcontext, KRB5_KDB_ACCESS_ERROR, "%s", ldapssl_err2string(st)); st = KRB5_KDB_ACCESS_ERROR; goto err_out; } /* ldap init, use SSL */ if ((st = ldap_initialize(&ldap_server_handle->ldap_handle, server_info->server_name, SSL_ON, &errstr)) != LDAP_SUCCESS) { if (ldap_context->kcontext) { krb5_set_error_message (ldap_context->kcontext, KRB5_KDB_ACCESS_ERROR, "%s", errstr); } st = KRB5_KDB_ACCESS_ERROR; goto err_out; } if (ldap_context->service_cert_path != NULL) { /* * Solaris Kerbreros: for LDAP_SASL_EXTERNAL bind which requires the * client offer its cert to the server. */ if ((st = ldapssl_enable_clientauth(ldap_server_handle->ldap_handle, NULL, ldap_context->service_cert_pass, "XXX WAF need cert nickname/label")) < 0) { if (ldap_context->kcontext) { krb5_set_error_message (ldap_context->kcontext, KRB5_KDB_ACCESS_ERROR, "%s", ldap_err2string(st)); } st = KRB5_KDB_ACCESS_ERROR; goto err_out; } } } if ((st=krb5_ldap_bind(ldap_context, ldap_server_handle)) == 0) { ldap_server_handle->server_info_update_pending = FALSE; server_info->server_status = ON; krb5_update_ldap_handle(ldap_server_handle, server_info); } else { if (ldap_context->kcontext) /* Solaris Kerberos: Better error message */ krb5_set_error_message (ldap_context->kcontext, KRB5_KDB_ACCESS_ERROR, gettext("Failed to bind to ldap server \"%s\": %s"), server_info->server_name, ldap_err2string(st)); st = KRB5_KDB_ACCESS_ERROR; server_info->server_status = OFF; time(&server_info->downtime); (void)ldap_unbind_s(ldap_server_handle->ldap_handle); free(ldap_server_handle); } err_out: return st; } /* * initialization for data base routines. */ krb5_error_code krb5_ldap_db_init(krb5_context context, krb5_ldap_context *ldap_context) { krb5_error_code st=0; krb5_boolean sasl_mech_supported=TRUE; int cnt=0, version=LDAP_VERSION3; #ifdef LDAP_OPT_NETWORK_TIMEOUT struct timeval local_timelimit = {10,0}; #elif defined LDAP_X_OPT_CONNECT_TIMEOUT int local_timelimit = 1000; /* Solaris Kerberos: 1 second */ #endif if ((st=krb5_validate_ldap_context(context, ldap_context)) != 0) goto err_out; ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &version); #ifdef LDAP_OPT_NETWORK_TIMEOUT ldap_set_option(NULL, LDAP_OPT_NETWORK_TIMEOUT, &local_timelimit); #elif defined LDAP_X_OPT_CONNECT_TIMEOUT ldap_set_option(NULL, LDAP_X_OPT_CONNECT_TIMEOUT, &local_timelimit); #endif HNDL_LOCK(ldap_context); while (ldap_context->server_info_list[cnt] != NULL) { krb5_ldap_server_info *server_info=NULL; server_info = ldap_context->server_info_list[cnt]; if (server_info->server_status == NOTSET) { int conns=0; /* * Check if the server has to perform certificate-based authentication */ if (ldap_context->service_cert_path != NULL) { /* Find out if the server supports SASL EXTERNAL mechanism */ if (has_sasl_external_mech(context, server_info->server_name) == 1) { cnt++; sasl_mech_supported = FALSE; continue; /* Check the next LDAP server */ } sasl_mech_supported = TRUE; } krb5_clear_error_message(context); for (conns=0; conns < ldap_context->max_server_conns; ++conns) { if ((st=krb5_ldap_initialize(ldap_context, server_info)) != 0) break; } /* for (conn= ... */ if (server_info->server_status == ON) break; /* server init successful, so break */ } ++cnt; } HNDL_UNLOCK(ldap_context); err_out: if (sasl_mech_supported == FALSE) { st = KRB5_KDB_ACCESS_ERROR; krb5_set_error_message (context, st, gettext("Certificate based authentication requested but " "not supported by LDAP servers")); } return (st); } /* * get a single handle. Do not lock the mutex */ krb5_error_code krb5_ldap_db_single_init(krb5_ldap_context *ldap_context) { krb5_error_code st=0; int cnt=0; krb5_ldap_server_info *server_info=NULL; while (ldap_context->server_info_list[cnt] != NULL) { server_info = ldap_context->server_info_list[cnt]; if ((server_info->server_status == NOTSET || server_info->server_status == ON)) { if (server_info->num_conns < ldap_context->max_server_conns-1) { st = krb5_ldap_initialize(ldap_context, server_info); if (st == LDAP_SUCCESS) goto cleanup; } } ++cnt; } /* If we are here, try to connect to all the servers */ cnt = 0; while (ldap_context->server_info_list[cnt] != NULL) { server_info = ldap_context->server_info_list[cnt]; st = krb5_ldap_initialize(ldap_context, server_info); if (st == LDAP_SUCCESS) goto cleanup; ++cnt; } cleanup: return (st); } krb5_error_code krb5_ldap_rebind(ldap_context, ldap_server_handle) krb5_ldap_context *ldap_context; krb5_ldap_server_handle **ldap_server_handle; { krb5_ldap_server_handle *handle = *ldap_server_handle; int use_ssl; /* * Solaris Kerberos: use SSL unless ldapi (unix domain sockets is specified) */ if (strncasecmp(handle->server_info->server_name, "ldapi:", 6) == 0) use_ssl = SSL_OFF; else use_ssl = SSL_ON; if ((ldap_initialize(&handle->ldap_handle, handle->server_info->server_name, use_ssl, NULL) != LDAP_SUCCESS) || (krb5_ldap_bind(ldap_context, handle) != LDAP_SUCCESS)) return krb5_ldap_request_next_handle_from_pool(ldap_context, ldap_server_handle); return LDAP_SUCCESS; } /* * DAL API functions */ krb5_error_code krb5_ldap_lib_init() { return 0; } krb5_error_code krb5_ldap_lib_cleanup() { /* right now, no cleanup required */ return 0; } krb5_error_code krb5_ldap_free_ldap_context(krb5_ldap_context *ldap_context) { if (ldap_context == NULL) return 0; krb5_ldap_free_krbcontainer_params(ldap_context->krbcontainer); ldap_context->krbcontainer = NULL; krb5_ldap_free_realm_params(ldap_context->lrparams); ldap_context->lrparams = NULL; krb5_ldap_free_server_params(ldap_context); return 0; } krb5_error_code krb5_ldap_close(krb5_context context) { kdb5_dal_handle *dal_handle=NULL; krb5_ldap_context *ldap_context=NULL; if (context == NULL || context->db_context == NULL || ((kdb5_dal_handle *)context->db_context)->db_context == NULL) return 0; dal_handle = (kdb5_dal_handle *) context->db_context; ldap_context = (krb5_ldap_context *) dal_handle->db_context; dal_handle->db_context = NULL; krb5_ldap_free_ldap_context(ldap_context); return 0; }