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 
47 static krb5_error_code
krb5_validate_ldap_context(krb5_context context,krb5_ldap_context * ldap_context)48 krb5_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 
112 err_out:
113     return st;
114 }
115 
116 /*
117  * Internal Functions called by init functions.
118  */
119 
120 static krb5_error_code
krb5_ldap_bind(ldap_context,ldap_server_handle)121 krb5_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 
160 static krb5_error_code
krb5_ldap_initialize(ldap_context,server_info)161 krb5_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 
263 err_out:
264     return st;
265 }
266 
267 /*
268  * initialization for data base routines.
269  */
270 
271 krb5_error_code
krb5_ldap_db_init(krb5_context context,krb5_ldap_context * ldap_context)272 krb5_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 
329 err_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 
344 krb5_error_code
krb5_ldap_db_single_init(krb5_ldap_context * ldap_context)345 krb5_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     }
373 cleanup:
374     return (st);
375 }
376 
377 krb5_error_code
krb5_ldap_rebind(ldap_context,ldap_server_handle)378 krb5_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  */
krb5_ldap_lib_init()403 krb5_error_code krb5_ldap_lib_init()
404 {
405     return 0;
406 }
407 
krb5_ldap_lib_cleanup()408 krb5_error_code krb5_ldap_lib_cleanup()
409 {
410     /* right now, no cleanup required */
411     return 0;
412 }
413 
414 krb5_error_code
krb5_ldap_free_ldap_context(krb5_ldap_context * ldap_context)415 krb5_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 
431 krb5_error_code
krb5_ldap_close(krb5_context context)432 krb5_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