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