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