xref: /illumos-gate/usr/src/lib/krb5/plugins/kdb/ldap/libkdb_ldap/kdb_ldap_conn.c (revision 54925bf60766fbb4f1f2d7c843721406a7b7a3fb)
1 /*
2  * Copyright 2007 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 	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 	krb5_set_error_message(context, st, gettext("LDAP bind password value missing "));
63 	goto err_out;
64     }
65 
66     if (ldap_context->bind_pwd == NULL &&
67 	ldap_context->service_password_file != NULL &&
68 	ldap_context->service_cert_path == NULL) {
69 
70 	if ((st=krb5_ldap_readpassword(context, ldap_context, &password)) != 0) {
71 	    prepend_err_str(context, gettext("Error reading password from stash: "), st, st);
72 	    goto err_out;
73 	}
74 
75 	/* Check if the returned 'password' is actually the path of a certificate */
76 	if (!strncmp("{FILE}", (char *)password, 6)) {
77 	    /* 'password' format: <path>\0<password> */
78 	    ldap_context->service_cert_path = strdup((char *)password + strlen("{FILE}"));
79 	    if (ldap_context->service_cert_path == NULL) {
80 		st = ENOMEM;
81 		krb5_set_error_message(context, st, gettext("Error: memory allocation failed"));
82 		goto err_out;
83 	    }
84 	    if (password[strlen((char *)password) + 1] == '\0')
85 		ldap_context->service_cert_pass = NULL;
86 	    else {
87 		ldap_context->service_cert_pass = strdup((char *)password +
88 							 strlen((char *)password) + 1);
89 		if (ldap_context->service_cert_pass == NULL) {
90 		    st = ENOMEM;
91 		    krb5_set_error_message(context, st, gettext("Error: memory allocation failed"));
92 		    goto err_out;
93 		}
94 	    }
95 	    free(password);
96 	} else {
97 	    ldap_context->bind_pwd = (char *)password;
98 	    if (ldap_context->bind_pwd == NULL) {
99 		st = EINVAL;
100 		krb5_set_error_message(context, st, gettext("Error reading password from stash"));
101 		goto err_out;
102 	    }
103 	}
104     }
105 
106     /* NULL password not allowed */
107     if (ldap_context->bind_pwd != NULL && strlen(ldap_context->bind_pwd) == 0) {
108 	st = EINVAL;
109 	krb5_set_error_message(context, st, gettext("Service password length is zero"));
110 	goto err_out;
111     }
112 
113 err_out:
114     return st;
115 }
116 
117 /*
118  * Internal Functions called by init functions.
119  */
120 
121 static krb5_error_code
122 krb5_ldap_bind(ldap_context, ldap_server_handle)
123     krb5_ldap_context           *ldap_context;
124     krb5_ldap_server_handle     *ldap_server_handle;
125 {
126     krb5_error_code             st=0;
127     struct berval               bv={0, NULL}, *servercreds=NULL;
128 
129     if (ldap_context->service_cert_path != NULL) {
130 	/* Certificate based bind (SASL EXTERNAL mechanism) */
131 
132 	st = ldap_sasl_bind_s(ldap_server_handle->ldap_handle,
133 			      NULL,	   /* Authenticating dn */
134 			      LDAP_SASL_EXTERNAL,  /* Method used for authentication */
135 			      &bv,
136 			      NULL,
137 			      NULL,
138 			      &servercreds);
139 
140 	while (st == LDAP_SASL_BIND_IN_PROGRESS) {
141 	    st = ldap_sasl_bind_s(ldap_server_handle->ldap_handle,
142 				  NULL,
143 				  LDAP_SASL_EXTERNAL,
144 				  servercreds,
145 				  NULL,
146 				  NULL,
147 				  &servercreds);
148 	}
149     } else {
150 	/* password based simple bind */
151         bv.bv_val = ldap_context->bind_pwd;
152         bv.bv_len = strlen(ldap_context->bind_pwd);
153         st = ldap_sasl_bind_s(ldap_server_handle->ldap_handle,
154                                 ldap_context->bind_dn,
155                                 LDAP_SASL_SIMPLE, &bv, NULL,
156                                 NULL, NULL);
157     }
158     return st;
159 }
160 
161 static krb5_error_code
162 krb5_ldap_initialize(ldap_context, server_info)
163     krb5_ldap_context *ldap_context;
164     krb5_ldap_server_info *server_info;
165 {
166     krb5_error_code             st=0;
167     krb5_ldap_server_handle     *ldap_server_handle=NULL;
168     char			*errstr = NULL;
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 	    krb5_set_error_message (ldap_context->kcontext,
252 				    KRB5_KDB_ACCESS_ERROR, "%s",
253 				    ldap_err2string(st));
254 	st = KRB5_KDB_ACCESS_ERROR;
255 	server_info->server_status = OFF;
256 	time(&server_info->downtime);
257 	(void)ldap_unbind_s(ldap_server_handle->ldap_handle);
258 	free(ldap_server_handle);
259     }
260 
261 err_out:
262     return st;
263 }
264 
265 /*
266  * initialization for data base routines.
267  */
268 
269 krb5_error_code
270 krb5_ldap_db_init(krb5_context context, krb5_ldap_context *ldap_context)
271 {
272     krb5_error_code             st=0;
273     krb5_boolean                sasl_mech_supported=TRUE;
274     int                         cnt=0, version=LDAP_VERSION3;
275     struct timeval              local_timelimit = {10,0};
276 
277     if ((st=krb5_validate_ldap_context(context, ldap_context)) != 0)
278 	goto err_out;
279 
280     ldap_set_option(NULL, LDAP_OPT_PROTOCOL_VERSION, &version);
281 #ifdef LDAP_OPT_NETWORK_TIMEOUT
282     ldap_set_option(NULL, LDAP_OPT_NETWORK_TIMEOUT, &local_timelimit);
283 #elif defined LDAP_X_OPT_CONNECT_TIMEOUT
284     ldap_set_option(NULL, LDAP_X_OPT_CONNECT_TIMEOUT, &local_timelimit);
285 #endif
286 
287     HNDL_LOCK(ldap_context);
288     while (ldap_context->server_info_list[cnt] != NULL) {
289 	krb5_ldap_server_info *server_info=NULL;
290 
291 	server_info = ldap_context->server_info_list[cnt];
292 
293 	if (server_info->server_status == NOTSET) {
294 	    int conns=0;
295 
296 	    /*
297 	     * Check if the server has to perform certificate-based authentication
298 	     */
299 	    if (ldap_context->service_cert_path != NULL) {
300 		/* Find out if the server supports SASL EXTERNAL mechanism */
301 		if (has_sasl_external_mech(context, server_info->server_name) == 1) {
302 		    cnt++;
303 		    sasl_mech_supported = FALSE;
304 		    continue; /* Check the next LDAP server */
305 		}
306 		sasl_mech_supported = TRUE;
307 	    }
308 
309 	    krb5_clear_error_message(context);
310 
311 	    for (conns=0; conns < ldap_context->max_server_conns; ++conns) {
312 		if ((st=krb5_ldap_initialize(ldap_context, server_info)) != 0)
313 		    break;
314 	    } /* for (conn= ... */
315 
316 	    if (server_info->server_status == ON)
317 		break;  /* server init successful, so break */
318 	}
319 	++cnt;
320     }
321     HNDL_UNLOCK(ldap_context);
322 
323 err_out:
324     if (sasl_mech_supported == FALSE) {
325 	st = KRB5_KDB_ACCESS_ERROR;
326 	krb5_set_error_message (context, st,
327 				gettext("Certificate based authentication requested but "
328 				"not supported by LDAP servers"));
329     }
330     return (st);
331 }
332 
333 
334 /*
335  * get a single handle. Do not lock the mutex
336  */
337 
338 krb5_error_code
339 krb5_ldap_db_single_init(krb5_ldap_context *ldap_context)
340 {
341     krb5_error_code             st=0;
342     int                         cnt=0;
343     krb5_ldap_server_info       *server_info=NULL;
344 
345     while (ldap_context->server_info_list[cnt] != NULL) {
346 	server_info = ldap_context->server_info_list[cnt];
347 	if ((server_info->server_status == NOTSET || server_info->server_status == ON)) {
348 	    if (server_info->num_conns < ldap_context->max_server_conns-1) {
349 		st = krb5_ldap_initialize(ldap_context, server_info);
350 		if (st == LDAP_SUCCESS)
351 		    goto cleanup;
352 	    }
353 	}
354 	++cnt;
355     }
356 
357     /* If we are here, try to connect to all the servers */
358 
359     cnt = 0;
360     while (ldap_context->server_info_list[cnt] != NULL) {
361 	server_info = ldap_context->server_info_list[cnt];
362 	st = krb5_ldap_initialize(ldap_context, server_info);
363 	if (st == LDAP_SUCCESS)
364 	    goto cleanup;
365 	++cnt;
366     }
367 cleanup:
368     return (st);
369 }
370 
371 krb5_error_code
372 krb5_ldap_rebind(ldap_context, ldap_server_handle)
373     krb5_ldap_context           *ldap_context;
374     krb5_ldap_server_handle     **ldap_server_handle;
375 {
376     krb5_ldap_server_handle     *handle = *ldap_server_handle;
377     int				use_ssl;
378 
379     /*
380      * Solaris Kerberos: use SSL unless ldapi (unix domain sockets is specified)
381      */
382     if (strncasecmp(handle->server_info->server_name, "ldapi:", 6) == 0)
383 	use_ssl = SSL_OFF;
384     else
385 	use_ssl = SSL_ON;
386 
387     if ((ldap_initialize(&handle->ldap_handle, handle->server_info->server_name,
388 		use_ssl, NULL) != LDAP_SUCCESS)
389 	|| (krb5_ldap_bind(ldap_context, handle) != LDAP_SUCCESS))
390 	return krb5_ldap_request_next_handle_from_pool(ldap_context, ldap_server_handle);
391     return LDAP_SUCCESS;
392 }
393 
394 /*
395  *     DAL API functions
396  */
397 krb5_error_code krb5_ldap_lib_init()
398 {
399     return 0;
400 }
401 
402 krb5_error_code krb5_ldap_lib_cleanup()
403 {
404     /* right now, no cleanup required */
405     return 0;
406 }
407 
408 krb5_error_code
409 krb5_ldap_free_ldap_context(krb5_ldap_context *ldap_context)
410 {
411     if (ldap_context == NULL)
412 	return 0;
413 
414     krb5_ldap_free_krbcontainer_params(ldap_context->krbcontainer);
415     ldap_context->krbcontainer = NULL;
416 
417     krb5_ldap_free_realm_params(ldap_context->lrparams);
418     ldap_context->lrparams = NULL;
419 
420     krb5_ldap_free_server_params(ldap_context);
421 
422     return 0;
423 }
424 
425 krb5_error_code
426 krb5_ldap_close(krb5_context context)
427 {
428     kdb5_dal_handle  *dal_handle=NULL;
429     krb5_ldap_context *ldap_context=NULL;
430 
431     if (context == NULL ||
432 	context->db_context == NULL ||
433 	((kdb5_dal_handle *)context->db_context)->db_context == NULL)
434 	return 0;
435 
436     dal_handle = (kdb5_dal_handle *) context->db_context;
437     ldap_context = (krb5_ldap_context *) dal_handle->db_context;
438     dal_handle->db_context = NULL;
439 
440     krb5_ldap_free_ldap_context(ldap_context);
441 
442     return 0;
443 }
444