1/*
2 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
3 * Use is subject to license terms.
4 */
5
6
7/*
8 * lib/kdb/kdb_ldap/kdb_ldap_conn.c
9 *
10 * Copyright (c) 2004-2005, Novell, Inc.
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are met:
15 *
16 *   * Redistributions of source code must retain the above copyright notice,
17 *       this list of conditions and the following disclaimer.
18 *   * Redistributions in binary form must reproduce the above copyright
19 *       notice, this list of conditions and the following disclaimer in the
20 *       documentation and/or other materials provided with the distribution.
21 *   * The copyright holder's name is not used to endorse or promote products
22 *       derived from this software without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 */
36
37#include "autoconf.h"
38#if HAVE_UNISTD_H
39#include <unistd.h>
40#endif
41
42#include "ldap_main.h"
43#include "ldap_service_stash.h"
44#include <kdb5.h>
45#include <libintl.h>
46
47static krb5_error_code
48krb5_validate_ldap_context(krb5_context context, krb5_ldap_context *ldap_context)
49{
50    krb5_error_code             st=0;
51    unsigned char               *password=NULL;
52
53    if (ldap_context->bind_dn == NULL) {
54	st = EINVAL;
55	/* Solaris Kerberos: Keep error messages consistent */
56	krb5_set_error_message(context, st, gettext("LDAP bind dn value missing"));
57	goto err_out;
58    }
59
60    if (ldap_context->bind_pwd == NULL && ldap_context->service_password_file == NULL) {
61	st = EINVAL;
62	/* Solaris Kerberos: Keep error messages consistent */
63	krb5_set_error_message(context, st, gettext("LDAP bind password value missing"));
64	goto err_out;
65    }
66
67    if (ldap_context->bind_pwd == NULL && ldap_context->service_password_file !=
68	NULL && ldap_context->service_cert_path == NULL) {
69	if ((st=krb5_ldap_readpassword(context, ldap_context, &password)) != 0) {
70	    prepend_err_str(context, gettext("Error reading password from stash: "), st, st);
71	    goto err_out;
72	}
73
74	/* Check if the returned 'password' is actually the path of a certificate */
75	if (!strncmp("{FILE}", (char *)password, 6)) {
76	    /* 'password' format: <path>\0<password> */
77	    ldap_context->service_cert_path = strdup((char *)password + strlen("{FILE}"));
78	    if (ldap_context->service_cert_path == NULL) {
79		st = ENOMEM;
80		krb5_set_error_message(context, st, gettext("Error: memory allocation failed"));
81		goto err_out;
82	    }
83	    if (password[strlen((char *)password) + 1] == '\0')
84		ldap_context->service_cert_pass = NULL;
85	    else {
86		ldap_context->service_cert_pass = strdup((char *)password +
87							 strlen((char *)password) + 1);
88		if (ldap_context->service_cert_pass == NULL) {
89		    st = ENOMEM;
90		    krb5_set_error_message(context, st, gettext("Error: memory allocation failed"));
91		    goto err_out;
92		}
93	    }
94	    free(password);
95	} else {
96	    ldap_context->bind_pwd = (char *)password;
97	    if (ldap_context->bind_pwd == NULL) {
98		st = EINVAL;
99		krb5_set_error_message(context, st, gettext("Error reading password from stash"));
100		goto err_out;
101	    }
102	}
103    }
104
105    /* NULL password not allowed */
106    if (ldap_context->bind_pwd != NULL && strlen(ldap_context->bind_pwd) == 0) {
107	st = EINVAL;
108	krb5_set_error_message(context, st, gettext("Service password length is zero"));
109	goto err_out;
110    }
111
112err_out:
113    return st;
114}
115
116/*
117 * Internal Functions called by init functions.
118 */
119
120static krb5_error_code
121krb5_ldap_bind(ldap_context, ldap_server_handle)
122    krb5_ldap_context           *ldap_context;
123    krb5_ldap_server_handle     *ldap_server_handle;
124{
125    krb5_error_code             st=0;
126    struct berval               bv={0, NULL}, *servercreds=NULL;
127
128    if (ldap_context->service_cert_path != NULL) {
129	/* Certificate based bind (SASL EXTERNAL mechanism) */
130
131	st = ldap_sasl_bind_s(ldap_server_handle->ldap_handle,
132			      NULL,	   /* Authenticating dn */
133			      LDAP_SASL_EXTERNAL,  /* Method used for authentication */
134			      &bv,
135			      NULL,
136			      NULL,
137			      &servercreds);
138
139	while (st == LDAP_SASL_BIND_IN_PROGRESS) {
140	    st = ldap_sasl_bind_s(ldap_server_handle->ldap_handle,
141				  NULL,
142				  LDAP_SASL_EXTERNAL,
143				  servercreds,
144				  NULL,
145				  NULL,
146				  &servercreds);
147	}
148    } else {
149	/* password based simple bind */
150        bv.bv_val = ldap_context->bind_pwd;
151        bv.bv_len = strlen(ldap_context->bind_pwd);
152        st = ldap_sasl_bind_s(ldap_server_handle->ldap_handle,
153                                ldap_context->bind_dn,
154                                LDAP_SASL_SIMPLE, &bv, NULL,
155                                NULL, NULL);
156    }
157    return st;
158}
159
160static krb5_error_code
161krb5_ldap_initialize(ldap_context, server_info)
162    krb5_ldap_context *ldap_context;
163    krb5_ldap_server_info *server_info;
164{
165    krb5_error_code             st=0;
166    krb5_ldap_server_handle     *ldap_server_handle=NULL;
167    char			*errstr = NULL;
168
169
170    ldap_server_handle = calloc(1, sizeof(krb5_ldap_server_handle));
171    if (ldap_server_handle == NULL) {
172	st = ENOMEM;
173	goto err_out;
174    }
175    else {
176	/*
177	 * Solaris Kerbreros: need ldap_handle to be NULL so calls to
178	 * ldap_initialize won't leak handles
179	 */
180	ldap_server_handle->ldap_handle = NULL;
181    }
182
183    if (strncasecmp(server_info->server_name, "ldapi:", 6) == 0) {
184	/*
185	 * Solaris Kerberos: ldapi is not supported on Solaris at this time.
186	 * return an error.
187	 */
188	if (ldap_context->kcontext)
189	    krb5_set_error_message (ldap_context->kcontext, KRB5_KDB_ACCESS_ERROR,
190		gettext("ldapi is not supported"));
191	st = KRB5_KDB_ACCESS_ERROR;
192	goto err_out;
193    } else {
194	/*
195	 * Solaris Kerbreros: need to use SSL to protect LDAP simple and
196	 * External binds.
197	 */
198	if (ldap_context->root_certificate_file == NULL) {
199	    if (ldap_context->kcontext)
200		krb5_set_error_message (ldap_context->kcontext, KRB5_KDB_ACCESS_ERROR,
201		    gettext("ldap_cert_path not set, can not create SSL connection"));
202	    st = KRB5_KDB_ACCESS_ERROR;
203	    goto err_out;
204	}
205
206	/* setup for SSL */
207	if ((st = ldapssl_client_init(ldap_context->root_certificate_file, NULL)) < 0) {
208	    if (ldap_context->kcontext)
209		krb5_set_error_message (ldap_context->kcontext, KRB5_KDB_ACCESS_ERROR, "%s",
210		    ldapssl_err2string(st));
211	    st = KRB5_KDB_ACCESS_ERROR;
212	    goto err_out;
213	}
214
215	/* ldap init, use SSL */
216	if ((st = ldap_initialize(&ldap_server_handle->ldap_handle,
217				    server_info->server_name, SSL_ON, &errstr)) != LDAP_SUCCESS) {
218	    if (ldap_context->kcontext) {
219		krb5_set_error_message (ldap_context->kcontext, KRB5_KDB_ACCESS_ERROR, "%s",
220		    errstr);
221	    }
222	    st = KRB5_KDB_ACCESS_ERROR;
223	    goto err_out;
224	}
225
226	if (ldap_context->service_cert_path != NULL) {
227	    /*
228	     * Solaris Kerbreros: for LDAP_SASL_EXTERNAL bind which requires the
229	     * client offer its cert to the server.
230	     */
231	    if ((st = ldapssl_enable_clientauth(ldap_server_handle->ldap_handle,
232					NULL, ldap_context->service_cert_pass,
233					"XXX WAF need cert nickname/label")) < 0) {
234		if (ldap_context->kcontext) {
235		    krb5_set_error_message (ldap_context->kcontext,
236					    KRB5_KDB_ACCESS_ERROR, "%s",
237					    ldap_err2string(st));
238		}
239		st = KRB5_KDB_ACCESS_ERROR;
240		goto err_out;
241	    }
242	}
243    }
244
245    if ((st=krb5_ldap_bind(ldap_context, ldap_server_handle)) == 0) {
246	ldap_server_handle->server_info_update_pending = FALSE;
247	server_info->server_status = ON;
248	krb5_update_ldap_handle(ldap_server_handle, server_info);
249    } else {
250	if (ldap_context->kcontext)
251	    /* Solaris Kerberos: Better error message */
252	    krb5_set_error_message (ldap_context->kcontext,
253				    KRB5_KDB_ACCESS_ERROR,
254				    gettext("Failed to bind to ldap server \"%s\": %s"),
255				    server_info->server_name, ldap_err2string(st));
256	st = KRB5_KDB_ACCESS_ERROR;
257	server_info->server_status = OFF;
258	time(&server_info->downtime);
259	(void)ldap_unbind_s(ldap_server_handle->ldap_handle);
260	free(ldap_server_handle);
261    }
262
263err_out:
264    return st;
265}
266
267/*
268 * initialization for data base routines.
269 */
270
271krb5_error_code
272krb5_ldap_db_init(krb5_context context, krb5_ldap_context *ldap_context)
273{
274    krb5_error_code             st=0;
275    krb5_boolean                sasl_mech_supported=TRUE;
276    int                         cnt=0, version=LDAP_VERSION3;
277#ifdef LDAP_OPT_NETWORK_TIMEOUT
278    struct timeval              local_timelimit = {10,0};
279#elif defined LDAP_X_OPT_CONNECT_TIMEOUT
280    int              		local_timelimit = 1000; /* Solaris Kerberos: 1 second */
281#endif
282
283    if ((st=krb5_validate_ldap_context(context, ldap_context)) != 0)
284	goto err_out;
285
286    ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &version);
287#ifdef LDAP_OPT_NETWORK_TIMEOUT
288    ldap_set_option(NULL, LDAP_OPT_NETWORK_TIMEOUT, &local_timelimit);
289#elif defined LDAP_X_OPT_CONNECT_TIMEOUT
290    ldap_set_option(NULL, LDAP_X_OPT_CONNECT_TIMEOUT, &local_timelimit);
291#endif
292
293    HNDL_LOCK(ldap_context);
294    while (ldap_context->server_info_list[cnt] != NULL) {
295	krb5_ldap_server_info *server_info=NULL;
296
297	server_info = ldap_context->server_info_list[cnt];
298
299	if (server_info->server_status == NOTSET) {
300	    int conns=0;
301
302	    /*
303	     * Check if the server has to perform certificate-based authentication
304	     */
305	    if (ldap_context->service_cert_path != NULL) {
306		/* Find out if the server supports SASL EXTERNAL mechanism */
307		if (has_sasl_external_mech(context, server_info->server_name) == 1) {
308		    cnt++;
309		    sasl_mech_supported = FALSE;
310		    continue; /* Check the next LDAP server */
311		}
312		sasl_mech_supported = TRUE;
313	    }
314
315	    krb5_clear_error_message(context);
316
317	    for (conns=0; conns < ldap_context->max_server_conns; ++conns) {
318		if ((st=krb5_ldap_initialize(ldap_context, server_info)) != 0)
319		    break;
320	    } /* for (conn= ... */
321
322	    if (server_info->server_status == ON)
323		break;  /* server init successful, so break */
324	}
325	++cnt;
326    }
327    HNDL_UNLOCK(ldap_context);
328
329err_out:
330    if (sasl_mech_supported == FALSE) {
331	st = KRB5_KDB_ACCESS_ERROR;
332	krb5_set_error_message (context, st,
333				gettext("Certificate based authentication requested but "
334				"not supported by LDAP servers"));
335    }
336    return (st);
337}
338
339
340/*
341 * get a single handle. Do not lock the mutex
342 */
343
344krb5_error_code
345krb5_ldap_db_single_init(krb5_ldap_context *ldap_context)
346{
347    krb5_error_code             st=0;
348    int                         cnt=0;
349    krb5_ldap_server_info       *server_info=NULL;
350
351    while (ldap_context->server_info_list[cnt] != NULL) {
352	server_info = ldap_context->server_info_list[cnt];
353	if ((server_info->server_status == NOTSET || server_info->server_status == ON)) {
354	    if (server_info->num_conns < ldap_context->max_server_conns-1) {
355		st = krb5_ldap_initialize(ldap_context, server_info);
356		if (st == LDAP_SUCCESS)
357		    goto cleanup;
358	    }
359	}
360	++cnt;
361    }
362
363    /* If we are here, try to connect to all the servers */
364
365    cnt = 0;
366    while (ldap_context->server_info_list[cnt] != NULL) {
367	server_info = ldap_context->server_info_list[cnt];
368	st = krb5_ldap_initialize(ldap_context, server_info);
369	if (st == LDAP_SUCCESS)
370	    goto cleanup;
371	++cnt;
372    }
373cleanup:
374    return (st);
375}
376
377krb5_error_code
378krb5_ldap_rebind(ldap_context, ldap_server_handle)
379    krb5_ldap_context           *ldap_context;
380    krb5_ldap_server_handle     **ldap_server_handle;
381{
382    krb5_ldap_server_handle     *handle = *ldap_server_handle;
383    int				use_ssl;
384
385    /*
386     * Solaris Kerberos: use SSL unless ldapi (unix domain sockets is specified)
387     */
388    if (strncasecmp(handle->server_info->server_name, "ldapi:", 6) == 0)
389	use_ssl = SSL_OFF;
390    else
391	use_ssl = SSL_ON;
392
393    if ((ldap_initialize(&handle->ldap_handle, handle->server_info->server_name,
394		use_ssl, NULL) != LDAP_SUCCESS)
395	|| (krb5_ldap_bind(ldap_context, handle) != LDAP_SUCCESS))
396	return krb5_ldap_request_next_handle_from_pool(ldap_context, ldap_server_handle);
397    return LDAP_SUCCESS;
398}
399
400/*
401 *     DAL API functions
402 */
403krb5_error_code krb5_ldap_lib_init()
404{
405    return 0;
406}
407
408krb5_error_code krb5_ldap_lib_cleanup()
409{
410    /* right now, no cleanup required */
411    return 0;
412}
413
414krb5_error_code
415krb5_ldap_free_ldap_context(krb5_ldap_context *ldap_context)
416{
417    if (ldap_context == NULL)
418	return 0;
419
420    krb5_ldap_free_krbcontainer_params(ldap_context->krbcontainer);
421    ldap_context->krbcontainer = NULL;
422
423    krb5_ldap_free_realm_params(ldap_context->lrparams);
424    ldap_context->lrparams = NULL;
425
426    krb5_ldap_free_server_params(ldap_context);
427
428    return 0;
429}
430
431krb5_error_code
432krb5_ldap_close(krb5_context context)
433{
434    kdb5_dal_handle  *dal_handle=NULL;
435    krb5_ldap_context *ldap_context=NULL;
436
437    if (context == NULL ||
438	context->db_context == NULL ||
439	((kdb5_dal_handle *)context->db_context)->db_context == NULL)
440	return 0;
441
442    dal_handle = (kdb5_dal_handle *) context->db_context;
443    ldap_context = (krb5_ldap_context *) dal_handle->db_context;
444    dal_handle->db_context = NULL;
445
446    krb5_ldap_free_ldap_context(ldap_context);
447
448    return 0;
449}
450