xref: /illumos-gate/usr/src/lib/krb5/plugins/kdb/ldap/libkdb_ldap/kdb_ldap.c (revision 7c64d3750da7fda7e450b8f9b0b963905ded6379)
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.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 <ctype.h>
44 #include "kdb_ldap.h"
45 #include "ldap_misc.h"
46 #include "ldap_main.h"
47 #include <kdb5.h>
48 #include <kadm5/admin.h>
49 /* Solaris Kerberos: needed for MAKE_INIT_FUNCTION() */
50 #include <k5-platform.h>
51 #include <k5-int.h>
52 #include <libintl.h>
53 
54 krb5_error_code
55 krb5_ldap_get_db_opt(char *input, char **opt, char **val)
56 {
57     char *pos = strchr(input, '=');
58 
59     *val = NULL;
60     if (pos == NULL) {
61 	*opt = strdup(input);
62 	if (*opt == NULL) {
63 	    return ENOMEM;
64 	}
65     } else {
66 	int len = pos - input;
67 	*opt = malloc((unsigned) len + 1);
68 	if (!*opt) {
69 	    return ENOMEM;
70 	}
71 	memcpy(*opt, input, (unsigned) len);
72 	/* ignore trailing blanks */
73 	while (isblank((*opt)[len-1]))
74 	    --len;
75 	(*opt)[len] = '\0';
76 
77 	pos += 1; /* move past '=' */
78 	while (isblank(*pos))  /* ignore leading blanks */
79 	    pos += 1;
80 	if (*pos != '\0') {
81 	    *val = strdup (pos);
82 	    if (!*val) {
83 		free (*opt);
84 		return ENOMEM;
85 	    }
86 	}
87     }
88     return (0);
89 
90 }
91 
92 
93 /*
94  * ldap get age
95  */
96 /*ARGSUSED*/
97 krb5_error_code
98 krb5_ldap_db_get_age(context, db_name, age)
99     krb5_context context;
100     char *db_name;
101     time_t *age;
102 {
103     time (age);
104     return 0;
105 }
106 
107 /*
108  * read startup information - kerberos and realm container
109  */
110 krb5_error_code
111 krb5_ldap_read_startup_information(krb5_context context)
112 {
113     krb5_error_code      retval = 0;
114     kdb5_dal_handle      *dal_handle=NULL;
115     krb5_ldap_context    *ldap_context=NULL;
116     int                  mask = 0;
117 
118     SETUP_CONTEXT();
119     if ((retval=krb5_ldap_read_krbcontainer_params(context, &(ldap_context->krbcontainer)))) {
120 	prepend_err_str (context, gettext("Unable to read Kerberos container"), retval, retval);
121 	goto cleanup;
122     }
123 
124     if ((retval=krb5_ldap_read_realm_params(context, context->default_realm, &(ldap_context->lrparams), &mask))) {
125 	prepend_err_str (context, gettext("Unable to read Realm"), retval, retval);
126 	goto cleanup;
127     }
128 
129     if (((mask & LDAP_REALM_MAXTICKETLIFE) == 0) || ((mask & LDAP_REALM_MAXRENEWLIFE) == 0)
130                                                  || ((mask & LDAP_REALM_KRBTICKETFLAGS) == 0)) {
131         kadm5_config_params  params_in, params_out;
132 
133         memset((char *) &params_in, 0, sizeof(params_in));
134         memset((char *) &params_out, 0, sizeof(params_out));
135 
136 	/* Solaris Kerberos: not supported yet */
137 #if 0 /************** Begin IFDEF'ed OUT *******************************/
138         retval = kadm5_get_config_params(context, 1, &params_in, &params_out);
139 #else
140         retval = kadm5_get_config_params(context, NULL, NULL, &params_in, &params_out);
141 #endif /**************** END IFDEF'ed OUT *******************************/
142         if (retval) {
143             if ((mask & LDAP_REALM_MAXTICKETLIFE) == 0) {
144                 ldap_context->lrparams->max_life = 24 * 60 * 60; /* 1 day */
145             }
146             if ((mask & LDAP_REALM_MAXRENEWLIFE) == 0) {
147                 ldap_context->lrparams->max_renewable_life = 0;
148             }
149             if ((mask & LDAP_REALM_KRBTICKETFLAGS) == 0) {
150                 ldap_context->lrparams->tktflags = KRB5_KDB_DEF_FLAGS;
151             }
152             retval = 0;
153             goto cleanup;
154         }
155 
156         if ((mask & LDAP_REALM_MAXTICKETLIFE) == 0) {
157             if (params_out.mask & KADM5_CONFIG_MAX_LIFE)
158                 ldap_context->lrparams->max_life = params_out.max_life;
159         }
160 
161         if ((mask & LDAP_REALM_MAXRENEWLIFE) == 0) {
162             if (params_out.mask & KADM5_CONFIG_MAX_RLIFE)
163                 ldap_context->lrparams->max_renewable_life = params_out.max_rlife;
164         }
165 
166         if ((mask & LDAP_REALM_KRBTICKETFLAGS) == 0) {
167             if (params_out.mask & KADM5_CONFIG_FLAGS)
168                 ldap_context->lrparams->tktflags = params_out.flags;
169         }
170 
171         kadm5_free_config_params(context, &params_out);
172     }
173 
174 cleanup:
175     return retval;
176 }
177 
178 
179 /* Function to check if a LDAP server supports the SASL external mechanism
180  *Return values:
181  *   0 => supports
182  *   1 => does not support
183  *   2 => don't know
184  */
185 #define ERR_MSG1 "Unable to check if SASL EXTERNAL mechanism is supported by LDAP server. Proceeding anyway ..."
186 #define ERR_MSG2 "SASL EXTERNAL mechanism not supported by LDAP server. Can't perform certificate-based bind."
187 
188 int
189 has_sasl_external_mech(context, ldap_server)
190     krb5_context     context;
191     char             *ldap_server;
192 {
193     int               i=0, flag=0, ret=0, retval=0;
194     char              *attrs[]={"supportedSASLMechanisms", NULL}, **values=NULL;
195     LDAP              *ld=NULL;
196     LDAPMessage       *msg=NULL, *res=NULL;
197 
198     /*
199      * Solaris Kerberos: don't use SSL since we are checking to see if SASL
200      * Externnal mech is supported.
201      */
202     retval = ldap_initialize(&ld, ldap_server, SSL_OFF, NULL);
203     if (retval != LDAP_SUCCESS) {
204 	krb5_set_error_message(context, 2, "%s", ERR_MSG1);
205 	ret = 2; /* Don't know */
206 	goto cleanup;
207     }
208 
209     /* Solaris Kerberos: anon bind not needed */
210 #if 0 /************** Begin IFDEF'ed OUT *******************************/
211     /* Anonymous bind */
212     retval = ldap_sasl_bind_s(ld, NULL, NULL, NULL, NULL, NULL, NULL);
213     if (retval != LDAP_SUCCESS) {
214 	krb5_set_error_message(context, 2, "%s", ERR_MSG1);
215 	ret = 2; /* Don't know */
216 	goto cleanup;
217     }
218 #endif /**************** END IFDEF'ed OUT *******************************/
219 
220     retval = ldap_search_ext_s(ld, "", LDAP_SCOPE_BASE, NULL, attrs, 0, NULL, NULL, NULL, 0, &res);
221     if (retval != LDAP_SUCCESS) {
222 	krb5_set_error_message(context, 2, "%s", ERR_MSG1);
223 	ret = 2; /* Don't know */
224 	goto cleanup;
225     }
226 
227 #if 0 /************** Begin IFDEF'ed OUT *******************************/
228     msg = ldap_first_message(ld, res);
229 #else
230     /* Solaris Kerberos: more accurate */
231     msg = ldap_first_entry(ld, res);
232 #endif /**************** END IFDEF'ed OUT *******************************/
233     if (msg == NULL) {
234 	krb5_set_error_message(context, 2, "%s", ERR_MSG1);
235 	ret = 2; /* Don't know */
236 	goto cleanup;
237     }
238 
239     values = ldap_get_values(ld, msg, "supportedSASLMechanisms");
240     if (values == NULL) {
241 	krb5_set_error_message(context, 1, "%s", ERR_MSG2);
242 	ret = 1; /* Not supported */
243 	goto cleanup;
244     }
245 
246     for (i = 0; values[i] != NULL; i++) {
247 	if (strcmp(values[i], "EXTERNAL"))
248 	    continue;
249 	flag = 1;
250     }
251 
252     if (flag != 1) {
253 	krb5_set_error_message(context, 1, "%s", ERR_MSG2);
254 	ret = 1; /* Not supported */
255 	goto cleanup;
256     }
257 
258 cleanup:
259 
260     if (values != NULL)
261 	ldap_value_free(values);
262 
263     if (res != NULL)
264 	ldap_msgfree(res);
265 
266     if (ld != NULL)
267 	ldap_unbind_ext_s(ld, NULL, NULL);
268 
269     return ret;
270 }
271 
272 /*ARGSUSED*/
273 void * krb5_ldap_alloc(krb5_context context, void *ptr, size_t size)
274 {
275     return realloc(ptr, size);
276 }
277 
278 /*ARGSUSED*/
279 void krb5_ldap_free(krb5_context context, void *ptr)
280 
281 {
282     free(ptr);
283 }
284 
285 krb5_error_code krb5_ldap_open(krb5_context context,
286 			       char *conf_section,
287 			       char **db_args,
288 			       int mode)
289 {
290     krb5_error_code status  = 0;
291     char **t_ptr = db_args;
292     krb5_ldap_context *ldap_context=NULL;
293     int srv_cnt = 0;
294     kdb5_dal_handle *dal_handle=NULL;
295 
296     /* Clear the global error string */
297     krb5_clear_error_message(context);
298 
299     ldap_context = calloc(1, sizeof(krb5_ldap_context));
300     if (ldap_context == NULL) {
301 	status = ENOMEM;
302 	goto clean_n_exit;
303     }
304 
305     ldap_context->kcontext = context;
306 
307     while (t_ptr && *t_ptr) {
308 	char *opt = NULL, *val = NULL;
309 
310 	if ((status = krb5_ldap_get_db_opt(*t_ptr, &opt, &val)) != 0) {
311 	    goto clean_n_exit;
312 	}
313 	if (opt && !strcmp(opt, "binddn")) {
314 	    if (ldap_context->bind_dn) {
315 		free (opt);
316 		free (val);
317 		status = EINVAL;
318 		krb5_set_error_message (context, status, gettext("'binddn' missing"));
319 		goto clean_n_exit;
320 	    }
321 	    if (val == NULL) {
322 		status = EINVAL;
323 		krb5_set_error_message (context, status, gettext("'binddn' value missing"));
324 		free(opt);
325 		goto clean_n_exit;
326 	    }
327 	    ldap_context->bind_dn = strdup(val);
328 	    if (ldap_context->bind_dn == NULL) {
329 		free (opt);
330 		free (val);
331 		status = ENOMEM;
332 		goto clean_n_exit;
333 	    }
334 	} else if (opt && !strcmp(opt, "nconns")) {
335 	    if (ldap_context->max_server_conns) {
336 		free (opt);
337 		free (val);
338 		status = EINVAL;
339 		krb5_set_error_message (context, status, gettext("'nconns' missing"));
340 		goto clean_n_exit;
341 	    }
342 	    if (val == NULL) {
343 		status = EINVAL;
344 		krb5_set_error_message (context, status, gettext("'nconns' value missing"));
345 		free(opt);
346 		goto clean_n_exit;
347 	    }
348 	    ldap_context->max_server_conns = atoi(val) ? atoi(val) : DEFAULT_CONNS_PER_SERVER;
349 	} else if (opt && !strcmp(opt, "bindpwd")) {
350 	    if (ldap_context->bind_pwd) {
351 		free (opt);
352 		free (val);
353 		status = EINVAL;
354 		krb5_set_error_message (context, status, gettext("'bindpwd' missing"));
355 		goto clean_n_exit;
356 	    }
357 	    if (val == NULL) {
358 		status = EINVAL;
359 		krb5_set_error_message (context, status, gettext("'bindpwd' value missing"));
360 		free(opt);
361 		goto clean_n_exit;
362 	    }
363 	    ldap_context->bind_pwd = strdup(val);
364 	    if (ldap_context->bind_pwd == NULL) {
365 		free (opt);
366 		free (val);
367 		status = ENOMEM;
368 		goto clean_n_exit;
369 	    }
370 	} else if (opt && !strcmp(opt, "host")) {
371 	    if (val == NULL) {
372 		status = EINVAL;
373 		krb5_set_error_message (context, status, gettext("'host' value missing"));
374 		free(opt);
375 		goto clean_n_exit;
376 	    }
377 	    if (ldap_context->server_info_list == NULL)
378 		ldap_context->server_info_list = (krb5_ldap_server_info **) calloc (SERV_COUNT+1, sizeof (krb5_ldap_server_info *)) ;
379 
380 	    if (ldap_context->server_info_list == NULL) {
381 		free (opt);
382 		free (val);
383 		status = ENOMEM;
384 		goto clean_n_exit;
385 	    }
386 
387 	    ldap_context->server_info_list[srv_cnt] = (krb5_ldap_server_info *) calloc (1, sizeof (krb5_ldap_server_info));
388 	    if (ldap_context->server_info_list[srv_cnt] == NULL) {
389 		free (opt);
390 		free (val);
391 		status = ENOMEM;
392 		goto clean_n_exit;
393 	    }
394 
395 	    ldap_context->server_info_list[srv_cnt]->server_status = NOTSET;
396 
397 	    ldap_context->server_info_list[srv_cnt]->server_name = strdup(val);
398 	    if (ldap_context->server_info_list[srv_cnt]->server_name == NULL) {
399 		free (opt);
400 		free (val);
401 		status = ENOMEM;
402 		goto clean_n_exit;
403 	    }
404 
405 	    srv_cnt++;
406 #ifdef HAVE_EDIRECTORY
407 	} else if (opt && !strcmp(opt, "cert")) {
408 	    if (val == NULL) {
409 		status = EINVAL;
410 		krb5_set_error_message (context, status, gettext("'cert' value missing"));
411 		free(opt);
412 		goto clean_n_exit;
413 	    }
414 
415 	    if (ldap_context->root_certificate_file == NULL) {
416 		ldap_context->root_certificate_file = strdup(val);
417 		if (ldap_context->root_certificate_file == NULL) {
418 		    free (opt);
419 		    free (val);
420 		    status = ENOMEM;
421 		    goto clean_n_exit;
422 		}
423 	    } else {
424 		void *tmp=NULL;
425 		char *oldstr = NULL;
426 		unsigned int len=0;
427 
428 		oldstr = strdup(ldap_context->root_certificate_file);
429 		if (oldstr == NULL) {
430 		    free (opt);
431 		    free (val);
432 		    status = ENOMEM;
433 		    goto clean_n_exit;
434 		}
435 
436 		tmp = ldap_context->root_certificate_file;
437 		len = strlen(ldap_context->root_certificate_file) + 2 + strlen(val);
438 		ldap_context->root_certificate_file = realloc(ldap_context->root_certificate_file,
439 							      len);
440 		if (ldap_context->root_certificate_file == NULL) {
441 		    free (tmp);
442 		    free (opt);
443 		    free (val);
444 		    status = ENOMEM;
445 		    goto clean_n_exit;
446 		}
447 		memset(ldap_context->root_certificate_file, 0, len);
448 		sprintf(ldap_context->root_certificate_file,"%s %s", oldstr, val);
449 		free (oldstr);
450 	    }
451 #endif
452 	} else {
453 	    /* ignore hash argument. Might have been passed from create */
454 	    status = EINVAL;
455 	    if (opt && !strcmp(opt, "temporary")) {
456 		/*
457 		 * temporary is passed in when kdb5_util load without -update is done.
458 		 * This is unsupported by the LDAP plugin.
459 		 */
460 		krb5_set_error_message (context, status,
461 					gettext("open of LDAP directory aborted, plugin requires -update argument"));
462 	    } else {
463 		krb5_set_error_message (context, status, gettext("unknown option \'%s\'"),
464 					opt?opt:val);
465 	    }
466 	    free(opt);
467 	    free(val);
468 	    goto clean_n_exit;
469 	}
470 
471 	free(opt);
472 	free(val);
473 	t_ptr++;
474     }
475 
476     dal_handle = (kdb5_dal_handle *) context->db_context;
477     dal_handle->db_context = ldap_context;
478     status = krb5_ldap_read_server_params(context, conf_section, mode & 0x0300);
479     if (status) {
480 	if (ldap_context)
481 	    krb5_ldap_free_ldap_context(ldap_context);
482 	ldap_context = NULL;
483 	dal_handle->db_context = NULL;
484 	prepend_err_str (context, gettext("Error reading LDAP server params: "), status, status);
485 	goto clean_n_exit;
486     }
487     if ((status=krb5_ldap_db_init(context, ldap_context)) != 0) {
488 	goto clean_n_exit;
489     }
490 
491     if ((status=krb5_ldap_read_startup_information(context)) != 0) {
492 	goto clean_n_exit;
493     }
494 
495 clean_n_exit:
496     /* may be clearing up is not required  db_fini might do it for us, check out */
497     if (status) {
498 	krb5_ldap_close(context);
499     }
500     return status;
501 }
502 
503 #include "ldap_err.h"
504 int
505 set_ldap_error (krb5_context ctx, int st, int op)
506 {
507     int translated_st = translate_ldap_error(st, op);
508     krb5_set_error_message(ctx, translated_st, "%s", ldap_err2string(st));
509     return translated_st;
510 }
511 
512 void
513 prepend_err_str (krb5_context ctx, const char *str, krb5_error_code err,
514 		 krb5_error_code oerr)
515 {
516     const char *omsg;
517     if (oerr == 0) oerr = err;
518     omsg = krb5_get_error_message (ctx, err);
519     krb5_set_error_message (ctx, err, "%s %s", str, omsg);
520     /* Solaris Kerberos: Memleak */
521     krb5_free_error_message(ctx, omsg);
522 }
523 
524 extern krb5int_access accessor;
525 MAKE_INIT_FUNCTION(kldap_init_fn);
526 
527 int kldap_init_fn(void)
528 {
529     /* Global (per-module) initialization.  */
530     return krb5int_accessor (&accessor, KRB5INT_ACCESS_VERSION);
531 }
532 
533 int kldap_ensure_initialized(void)
534 {
535     return CALL_INIT_FUNCTION (kldap_init_fn);
536 }
537