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