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
53krb5_error_code
54krb5_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*/
96krb5_error_code
97krb5_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 */
109krb5_error_code
110krb5_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
168cleanup:
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
182int
183has_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
252cleanup:
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*/
267void * krb5_ldap_alloc(krb5_context context, void *ptr, size_t size)
268{
269    return realloc(ptr, size);
270}
271
272/*ARGSUSED*/
273void krb5_ldap_free(krb5_context context, void *ptr)
274
275{
276    free(ptr);
277}
278
279krb5_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
489clean_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"
498int
499set_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
506void
507prepend_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
518extern krb5int_access accessor;
519MAKE_INIT_FUNCTION(kldap_init_fn);
520
521int kldap_init_fn(void)
522{
523    /* Global (per-module) initialization.  */
524    return krb5int_accessor (&accessor, KRB5INT_ACCESS_VERSION);
525}
526
527int kldap_ensure_initialized(void)
528{
529    return CALL_INIT_FUNCTION (kldap_init_fn);
530}
531