Imported Upstream version 1.10.2
[platform/upstream/krb5.git] / src / plugins / kdb / ldap / libkdb_ldap / kdb_ldap.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/kdb/ldap/libkdb_ldap/kdb_ldap.c */
3 /*
4  * Copyright (c) 2004-2005, Novell, Inc.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  *   * Redistributions of source code must retain the above copyright notice,
11  *       this list of conditions and the following disclaimer.
12  *   * Redistributions in binary form must reproduce the above copyright
13  *       notice, this list of conditions and the following disclaimer in the
14  *       documentation and/or other materials provided with the distribution.
15  *   * The copyright holder's name is not used to endorse or promote products
16  *       derived from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "autoconf.h"
32 #if HAVE_UNISTD_H
33 #include <unistd.h>
34 #endif
35
36 #include <ctype.h>
37 #include "kdb_ldap.h"
38 #include "ldap_misc.h"
39 #include <kdb5.h>
40 #include <kadm5/admin.h>
41
42 #if !defined(isblank) && defined(HAVE_ISBLANK)
43 #if defined(NEED_ISBLANK_PROTO)
44 extern int isblank();
45 #endif
46 #else /* isblank missing */
47 #if !defined(isblank)
48 #define isblank isspace
49 #endif
50 #endif
51
52 krb5_error_code
53 krb5_ldap_get_db_opt(char *input, char **opt, char **val)
54 {
55     char *pos = strchr(input, '=');
56
57     *val = NULL;
58     if (pos == NULL) {
59         *opt = strdup(input);
60         if (*opt == NULL) {
61             return ENOMEM;
62         }
63     } else {
64         int len = pos - input;
65         *opt = malloc((unsigned) len + 1);
66         if (!*opt) {
67             return ENOMEM;
68         }
69         memcpy(*opt, input, (unsigned) len);
70         /* ignore trailing blanks */
71         while (isblank((*opt)[len-1]))
72             --len;
73         (*opt)[len] = '\0';
74
75         pos += 1; /* move past '=' */
76         while (isblank(*pos))  /* ignore leading blanks */
77             pos += 1;
78         if (*pos != '\0') {
79             *val = strdup (pos);
80             if (!*val) {
81                 free (*opt);
82                 return ENOMEM;
83             }
84         }
85     }
86     return (0);
87
88 }
89
90
91 /*
92  * ldap get age
93  */
94 krb5_error_code
95 krb5_ldap_get_age(context, db_name, age)
96     krb5_context context;
97     char *db_name;
98     time_t *age;
99 {
100     time (age);
101     return 0;
102 }
103
104 /*
105  * read startup information - kerberos and realm container
106  */
107 krb5_error_code
108 krb5_ldap_read_startup_information(krb5_context context)
109 {
110     krb5_error_code      retval = 0;
111     kdb5_dal_handle      *dal_handle=NULL;
112     krb5_ldap_context    *ldap_context=NULL;
113     int                  mask = 0;
114
115     SETUP_CONTEXT();
116     if ((retval=krb5_ldap_read_krbcontainer_params(context, &(ldap_context->krbcontainer)))) {
117         prepend_err_str(context, _("Unable to read Kerberos container"),
118                         retval, retval);
119         goto cleanup;
120     }
121
122     if ((retval=krb5_ldap_read_realm_params(context, context->default_realm, &(ldap_context->lrparams), &mask))) {
123         prepend_err_str(context, _("Unable to read Realm"), retval, retval);
124         goto cleanup;
125     }
126
127     if (((mask & LDAP_REALM_MAXTICKETLIFE) == 0) || ((mask & LDAP_REALM_MAXRENEWLIFE) == 0)
128         || ((mask & LDAP_REALM_KRBTICKETFLAGS) == 0)) {
129         kadm5_config_params  params_in, params_out;
130
131         memset(&params_in, 0, sizeof(params_in));
132         memset(&params_out, 0, sizeof(params_out));
133
134         retval = kadm5_get_config_params(context, 1, &params_in, &params_out);
135         if (retval) {
136             if ((mask & LDAP_REALM_MAXTICKETLIFE) == 0) {
137                 ldap_context->lrparams->max_life = 24 * 60 * 60; /* 1 day */
138             }
139             if ((mask & LDAP_REALM_MAXRENEWLIFE) == 0) {
140                 ldap_context->lrparams->max_renewable_life = 0;
141             }
142             if ((mask & LDAP_REALM_KRBTICKETFLAGS) == 0) {
143                 ldap_context->lrparams->tktflags = KRB5_KDB_DEF_FLAGS;
144             }
145             retval = 0;
146             goto cleanup;
147         }
148
149         if ((mask & LDAP_REALM_MAXTICKETLIFE) == 0) {
150             if (params_out.mask & KADM5_CONFIG_MAX_LIFE)
151                 ldap_context->lrparams->max_life = params_out.max_life;
152         }
153
154         if ((mask & LDAP_REALM_MAXRENEWLIFE) == 0) {
155             if (params_out.mask & KADM5_CONFIG_MAX_RLIFE)
156                 ldap_context->lrparams->max_renewable_life = params_out.max_rlife;
157         }
158
159         if ((mask & LDAP_REALM_KRBTICKETFLAGS) == 0) {
160             if (params_out.mask & KADM5_CONFIG_FLAGS)
161                 ldap_context->lrparams->tktflags = params_out.flags;
162         }
163
164         kadm5_free_config_params(context, &params_out);
165     }
166
167 cleanup:
168     return retval;
169 }
170
171
172 /*
173  * Interrogate the root DSE (zero length DN) for an attribute
174  * value assertion.
175  */
176 static int
177 has_rootdse_ava(krb5_context context, char *ldap_server, char *attribute,
178                 char *value)
179 {
180     int               i=0, flag=0, ret=0, retval=0;
181     char              *attrs[2], **values=NULL;
182     LDAP              *ld=NULL;
183     LDAPMessage       *msg=NULL, *res=NULL;
184     struct berval     cred;
185
186     attrs[0] = attribute;
187     attrs[1] = NULL;
188
189     retval = ldap_initialize(&ld, ldap_server);
190     if (retval != LDAP_SUCCESS) {
191         ret = 2; /* Don't know */
192         goto cleanup;
193     }
194
195     cred.bv_val = "";
196     cred.bv_len = 0;
197
198     /* Anonymous bind */
199     retval = ldap_sasl_bind_s(ld, "", NULL, &cred, NULL, NULL, NULL);
200     if (retval != LDAP_SUCCESS) {
201         ret = 2; /* Don't know */
202         goto cleanup;
203     }
204
205     retval = ldap_search_ext_s(ld, "", LDAP_SCOPE_BASE, NULL, attrs, 0, NULL, NULL, NULL, 0, &res);
206     if (retval != LDAP_SUCCESS) {
207         ret = 2; /* Don't know */
208         goto cleanup;
209     }
210
211     msg = ldap_first_message(ld, res);
212     if (msg == NULL) {
213         ret = 2; /* Don't know */
214         goto cleanup;
215     }
216
217     values = ldap_get_values(ld, msg, attribute);
218     if (values == NULL) {
219         ret = 1; /* Not supported */
220         goto cleanup;
221     }
222
223     for (i = 0; values[i] != NULL; i++) {
224         if (strcmp(values[i], value) == 0) {
225             flag = 1;
226             break;
227         }
228     }
229
230     if (flag != 1) {
231         ret = 1; /* Not supported */
232         goto cleanup;
233     }
234
235 cleanup:
236
237     if (values != NULL)
238         ldap_value_free(values);
239
240     if (res != NULL)
241         ldap_msgfree(res);
242
243     if (ld != NULL)
244         ldap_unbind_ext_s(ld, NULL, NULL);
245
246     return ret;
247 }
248
249 #define ERR_MSG1 _("Unable to check if SASL EXTERNAL mechanism is supported by LDAP server. Proceeding anyway ...")
250 #define ERR_MSG2 _("SASL EXTERNAL mechanism not supported by LDAP server. Can't perform certificate-based bind.")
251
252 /* Function to check if a LDAP server supports the SASL external mechanism
253  *Return values:
254  *   0 => supports
255  *   1 => does not support
256  *   2 => don't know
257  */
258 int
259 has_sasl_external_mech(krb5_context context, char *ldap_server)
260 {
261     int ret;
262
263     ret = has_rootdse_ava(context, ldap_server,
264                           "supportedSASLMechanisms", "EXTERNAL");
265     switch (ret) {
266     case 1: /* not supported */
267         krb5_set_error_message(context, 1, "%s", ERR_MSG2);
268         break;
269     case 2: /* don't know */
270         krb5_set_error_message(context, 1, "%s", ERR_MSG1);
271         break;
272     default:
273         break;
274     }
275
276     return ret;
277 }
278
279 int
280 has_modify_increment(context, ldap_server)
281     krb5_context     context;
282     char             *ldap_server;
283 {
284     return has_rootdse_ava(context, ldap_server,
285                            "supportedFeatures", "1.3.6.1.1.14");
286 }
287
288 void *
289 krb5_ldap_alloc(krb5_context context, void *ptr, size_t size)
290 {
291     return realloc(ptr, size);
292 }
293
294 void
295 krb5_ldap_free(krb5_context context, void *ptr)
296 {
297     free(ptr);
298 }
299
300 krb5_error_code
301 krb5_ldap_open(krb5_context context, char *conf_section, char **db_args,
302                int mode)
303 {
304     krb5_error_code status  = 0;
305     char **t_ptr = db_args;
306     krb5_ldap_context *ldap_context=NULL;
307     int srv_cnt = 0;
308     kdb5_dal_handle *dal_handle=NULL;
309
310     /* Clear the global error string */
311     krb5_clear_error_message(context);
312
313     ldap_context = calloc(1, sizeof(krb5_ldap_context));
314     if (ldap_context == NULL) {
315         status = ENOMEM;
316         goto clean_n_exit;
317     }
318
319     ldap_context->kcontext = context;
320
321     while (t_ptr && *t_ptr) {
322         char *opt = NULL, *val = NULL;
323
324         if ((status = krb5_ldap_get_db_opt(*t_ptr, &opt, &val)) != 0) {
325             goto clean_n_exit;
326         }
327         if (opt && !strcmp(opt, "binddn")) {
328             if (ldap_context->bind_dn) {
329                 free (opt);
330                 free (val);
331                 status = EINVAL;
332                 krb5_set_error_message(context, status, _("'binddn' missing"));
333                 goto clean_n_exit;
334             }
335             if (val == NULL) {
336                 status = EINVAL;
337                 krb5_set_error_message(context, status,
338                                        _("'binddn' value missing"));
339                 free(opt);
340                 goto clean_n_exit;
341             }
342             ldap_context->bind_dn = strdup(val);
343             if (ldap_context->bind_dn == NULL) {
344                 free (opt);
345                 free (val);
346                 status = ENOMEM;
347                 goto clean_n_exit;
348             }
349         } else if (opt && !strcmp(opt, "nconns")) {
350             if (ldap_context->max_server_conns) {
351                 free (opt);
352                 free (val);
353                 status = EINVAL;
354                 krb5_set_error_message(context, status, _("'nconns' missing"));
355                 goto clean_n_exit;
356             }
357             if (val == NULL) {
358                 status = EINVAL;
359                 krb5_set_error_message(context, status,
360                                        _("'nconns' value missing"));
361                 free(opt);
362                 goto clean_n_exit;
363             }
364             ldap_context->max_server_conns = atoi(val) ? atoi(val) : DEFAULT_CONNS_PER_SERVER;
365         } else if (opt && !strcmp(opt, "bindpwd")) {
366             if (ldap_context->bind_pwd) {
367                 free (opt);
368                 free (val);
369                 status = EINVAL;
370                 krb5_set_error_message(context, status,
371                                        _("'bindpwd' missing"));
372                 goto clean_n_exit;
373             }
374             if (val == NULL) {
375                 status = EINVAL;
376                 krb5_set_error_message(context, status,
377                                        _("'bindpwd' value missing"));
378                 free(opt);
379                 goto clean_n_exit;
380             }
381             ldap_context->bind_pwd = strdup(val);
382             if (ldap_context->bind_pwd == NULL) {
383                 free (opt);
384                 free (val);
385                 status = ENOMEM;
386                 goto clean_n_exit;
387             }
388         } else if (opt && !strcmp(opt, "host")) {
389             if (val == NULL) {
390                 status = EINVAL;
391                 krb5_set_error_message(context, status,
392                                        _("'host' value missing"));
393                 free(opt);
394                 goto clean_n_exit;
395             }
396             if (ldap_context->server_info_list == NULL)
397                 ldap_context->server_info_list = (krb5_ldap_server_info **) calloc (SERV_COUNT+1, sizeof (krb5_ldap_server_info *)) ;
398
399             if (ldap_context->server_info_list == NULL) {
400                 free (opt);
401                 free (val);
402                 status = ENOMEM;
403                 goto clean_n_exit;
404             }
405
406             ldap_context->server_info_list[srv_cnt] = (krb5_ldap_server_info *) calloc (1, sizeof (krb5_ldap_server_info));
407             if (ldap_context->server_info_list[srv_cnt] == NULL) {
408                 free (opt);
409                 free (val);
410                 status = ENOMEM;
411                 goto clean_n_exit;
412             }
413
414             ldap_context->server_info_list[srv_cnt]->server_status = NOTSET;
415
416             ldap_context->server_info_list[srv_cnt]->server_name = strdup(val);
417             if (ldap_context->server_info_list[srv_cnt]->server_name == NULL) {
418                 free (opt);
419                 free (val);
420                 status = ENOMEM;
421                 goto clean_n_exit;
422             }
423
424             srv_cnt++;
425 #ifdef HAVE_EDIRECTORY
426         } else if (opt && !strcmp(opt, "cert")) {
427             if (val == NULL) {
428                 status = EINVAL;
429                 krb5_set_error_message(context, status,
430                                        _("'cert' value missing"));
431                 free(opt);
432                 goto clean_n_exit;
433             }
434
435             if (ldap_context->root_certificate_file == NULL) {
436                 ldap_context->root_certificate_file = strdup(val);
437                 if (ldap_context->root_certificate_file == NULL) {
438                     free (opt);
439                     free (val);
440                     status = ENOMEM;
441                     goto clean_n_exit;
442                 }
443             } else {
444                 char *newstr;
445
446                 if (asprintf(&newstr, "%s %s",
447                              ldap_context->root_certificate_file, val) < 0) {
448                     free (opt);
449                     free (val);
450                     status = ENOMEM;
451                     goto clean_n_exit;
452                 }
453                 free(ldap_context->root_certificate_file);
454                 ldap_context->root_certificate_file = newstr;
455             }
456 #endif
457         } else {
458             /* ignore hash argument. Might have been passed from create */
459             status = EINVAL;
460             if (opt && !strcmp(opt, "temporary")) {
461                 /*
462                  * temporary is passed in when kdb5_util load without -update is done.
463                  * This is unsupported by the LDAP plugin.
464                  */
465                 krb5_set_error_message(context, status,
466                                        _("open of LDAP directory aborted, "
467                                          "plugin requires -update argument"));
468             } else {
469                 krb5_set_error_message (context, status,
470                                         _("unknown option \'%s\'"),
471                                         opt?opt:val);
472             }
473             free(opt);
474             free(val);
475             goto clean_n_exit;
476         }
477
478         free(opt);
479         free(val);
480         t_ptr++;
481     }
482
483     dal_handle = context->dal_handle;
484     dal_handle->db_context = ldap_context;
485     status = krb5_ldap_read_server_params(context, conf_section, mode & 0x0300);
486     if (status) {
487         if (ldap_context)
488             krb5_ldap_free_ldap_context(ldap_context);
489         ldap_context = NULL;
490         dal_handle->db_context = NULL;
491         prepend_err_str(context, _("Error reading LDAP server params: "),
492                         status, status);
493         goto clean_n_exit;
494     }
495     if ((status=krb5_ldap_db_init(context, ldap_context)) != 0) {
496         goto clean_n_exit;
497     }
498
499     if ((status=krb5_ldap_read_startup_information(context)) != 0) {
500         goto clean_n_exit;
501     }
502
503 clean_n_exit:
504     /* may be clearing up is not required  db_fini might do it for us, check out */
505     if (status) {
506         krb5_ldap_close(context);
507     }
508     return status;
509 }
510
511 #include "ldap_err.h"
512 int
513 set_ldap_error(krb5_context ctx, int st, int op)
514 {
515     int translated_st = translate_ldap_error(st, op);
516     krb5_set_error_message(ctx, translated_st, "%s", ldap_err2string(st));
517     return translated_st;
518 }
519
520 void
521 prepend_err_str(krb5_context ctx, const char *str, krb5_error_code err,
522                 krb5_error_code oerr)
523 {
524     const char *omsg;
525     if (oerr == 0) oerr = err;
526     omsg = krb5_get_error_message (ctx, err);
527     krb5_set_error_message (ctx, err, "%s %s", str, omsg);
528 }
529
530 extern krb5int_access accessor;
531 MAKE_INIT_FUNCTION(kldap_init_fn);
532
533 int
534 kldap_init_fn(void)
535 {
536     /* Global (per-module) initialization.  */
537     return krb5int_accessor (&accessor, KRB5INT_ACCESS_VERSION);
538 }
539
540 int
541 kldap_ensure_initialized(void)
542 {
543     return CALL_INIT_FUNCTION (kldap_init_fn);
544 }
545
546 krb5_error_code
547 krb5_ldap_check_policy_as(krb5_context kcontext, krb5_kdc_req *request,
548                           krb5_db_entry *client, krb5_db_entry *server,
549                           krb5_timestamp kdc_time, const char **status,
550                           krb5_pa_data ***e_data)
551 {
552     krb5_error_code retval;
553
554     retval = krb5_ldap_lockout_check_policy(kcontext, client, kdc_time);
555     if (retval == KRB5KDC_ERR_CLIENT_REVOKED)
556         *status = "LOCKED_OUT";
557     return retval;
558 }
559
560 void
561 krb5_ldap_audit_as_req(krb5_context kcontext, krb5_kdc_req *request,
562                        krb5_db_entry *client, krb5_db_entry *server,
563                        krb5_timestamp authtime, krb5_error_code error_code)
564 {
565     (void) krb5_ldap_lockout_audit(kcontext, client, authtime, error_code);
566 }
567
568 krb5_error_code
569 krb5_ldap_check_allowed_to_delegate(krb5_context context,
570                                     krb5_const_principal client,
571                                     const krb5_db_entry *server,
572                                     krb5_const_principal proxy)
573 {
574     krb5_error_code code;
575     krb5_tl_data *tlp;
576
577     code = KRB5KDC_ERR_POLICY;
578
579     for (tlp = server->tl_data; tlp != NULL; tlp = tlp->tl_data_next) {
580         krb5_principal acl;
581
582         if (tlp->tl_data_type != KRB5_TL_CONSTRAINED_DELEGATION_ACL)
583             continue;
584
585         if (krb5_parse_name(context, (char *)tlp->tl_data_contents, &acl) != 0)
586             continue;
587
588         if (krb5_principal_compare(context, proxy, acl)) {
589             code = 0;
590             krb5_free_principal(context, acl);
591             break;
592         }
593         krb5_free_principal(context, acl);
594     }
595
596     return code;
597 }