1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* plugins/kdb/ldap/libkdb_ldap/ldap_principal.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 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
32 * Use is subject to license terms.
33 */
34
35 #include "ldap_main.h"
36 #include "kdb_ldap.h"
37 #include "ldap_principal.h"
38 #include "princ_xdr.h"
39 #include "ldap_err.h"
40
41 struct timeval timelimit = {300, 0}; /* 5 minutes */
42 char *principal_attributes[] = { "krbprincipalname",
43 "krbcanonicalname",
44 "objectclass",
45 "krbprincipalkey",
46 "krbmaxrenewableage",
47 "krbmaxticketlife",
48 "krbticketflags",
49 "krbprincipalexpiration",
50 "krbticketpolicyreference",
51 "krbUpEnabled",
52 "krbpwdpolicyreference",
53 "krbpasswordexpiration",
54 "krbLastFailedAuth",
55 "krbLoginFailedCount",
56 "krbLastSuccessfulAuth",
57 "nsAccountLock",
58 "krbLastPwdChange",
59 "krbLastAdminUnlock",
60 "krbPrincipalAuthInd",
61 "krbExtraData",
62 "krbObjectReferences",
63 "krbAllowedToDelegateTo",
64 "krbPwdHistory",
65 NULL };
66
67 /* Must match KDB_*_ATTR macros in ldap_principal.h. */
68 static char *attributes_set[] = { "krbmaxticketlife",
69 "krbmaxrenewableage",
70 "krbticketflags",
71 "krbprincipalexpiration",
72 "krbticketpolicyreference",
73 "krbPrincipalAuthInd",
74 "krbpwdpolicyreference",
75 "krbpasswordexpiration",
76 "krbprincipalkey",
77 "krblastpwdchange",
78 "krbextradata",
79 "krbLastSuccessfulAuth",
80 "krbLastFailedAuth",
81 "krbLoginFailedCount",
82 "krbLastAdminUnlock",
83 "krbPwdHistory",
84 NULL };
85
86
87 static void
k5_free_key_data_contents(krb5_key_data * key)88 k5_free_key_data_contents(krb5_key_data *key)
89 {
90 int16_t i;
91
92 for (i = 0; i < key->key_data_ver; i++) {
93 zapfree(key->key_data_contents[i], key->key_data_length[i]);
94 key->key_data_contents[i] = NULL;
95 }
96 }
97
98 void
k5_free_key_data(krb5_int16 n_key_data,krb5_key_data * key_data)99 k5_free_key_data(krb5_int16 n_key_data, krb5_key_data *key_data)
100 {
101 int16_t i;
102
103 if (key_data == NULL)
104 return;
105 for (i = 0; i < n_key_data; i++)
106 k5_free_key_data_contents(&key_data[i]);
107 free(key_data);
108 }
109
110 void
krb5_dbe_free_contents(krb5_context context,krb5_db_entry * entry)111 krb5_dbe_free_contents(krb5_context context, krb5_db_entry *entry)
112 {
113 krb5_tl_data *tl_data_next=NULL;
114 krb5_tl_data *tl_data=NULL;
115
116 if (entry->e_data)
117 free(entry->e_data);
118 if (entry->princ)
119 krb5_free_principal(context, entry->princ);
120 for (tl_data = entry->tl_data; tl_data; tl_data = tl_data_next) {
121 tl_data_next = tl_data->tl_data_next;
122 if (tl_data->tl_data_contents)
123 free(tl_data->tl_data_contents);
124 free(tl_data);
125 }
126 k5_free_key_data(entry->n_key_data, entry->key_data);
127 memset(entry, 0, sizeof(*entry));
128 return;
129 }
130
131
132 krb5_error_code
krb5_ldap_iterate(krb5_context context,char * match_expr,krb5_error_code (* func)(krb5_pointer,krb5_db_entry *),krb5_pointer func_arg,krb5_flags iterflags)133 krb5_ldap_iterate(krb5_context context, char *match_expr,
134 krb5_error_code (*func)(krb5_pointer, krb5_db_entry *),
135 krb5_pointer func_arg, krb5_flags iterflags)
136 {
137 krb5_db_entry entry;
138 krb5_principal principal;
139 char **subtree=NULL, *princ_name=NULL, *realm=NULL, **values=NULL, *filter=NULL;
140 unsigned int tree=0, ntree=1, i=0;
141 krb5_error_code st=0, tempst=0;
142 LDAP *ld=NULL;
143 LDAPMessage *result=NULL, *ent=NULL;
144 kdb5_dal_handle *dal_handle=NULL;
145 krb5_ldap_context *ldap_context=NULL;
146 krb5_ldap_server_handle *ldap_server_handle=NULL;
147 char *default_match_expr = "*";
148
149 /* Clear the global error string */
150 krb5_clear_error_message(context);
151
152 memset(&entry, 0, sizeof(krb5_db_entry));
153 SETUP_CONTEXT();
154
155 realm = ldap_context->lrparams->realm_name;
156 if (realm == NULL) {
157 realm = context->default_realm;
158 if (realm == NULL) {
159 st = EINVAL;
160 k5_setmsg(context, st, _("Default realm not set"));
161 goto cleanup;
162 }
163 }
164
165 /*
166 * If no match_expr then iterate through all krb princs like the db2 plugin
167 */
168 if (match_expr == NULL)
169 match_expr = default_match_expr;
170
171 if (asprintf(&filter, FILTER"%s))", match_expr) < 0)
172 filter = NULL;
173 CHECK_NULL(filter);
174
175 if ((st = krb5_get_subtree_info(ldap_context, &subtree, &ntree)) != 0)
176 goto cleanup;
177
178 GET_HANDLE();
179
180 for (tree=0; tree < ntree; ++tree) {
181
182 LDAP_SEARCH(subtree[tree], ldap_context->lrparams->search_scope, filter, principal_attributes);
183 for (ent=ldap_first_entry(ld, result); ent != NULL; ent=ldap_next_entry(ld, ent)) {
184 values=ldap_get_values(ld, ent, "krbcanonicalname");
185 if (values == NULL)
186 values=ldap_get_values(ld, ent, "krbprincipalname");
187 if (values != NULL) {
188 for (i=0; values[i] != NULL; ++i) {
189 if (krb5_ldap_parse_principal_name(values[i], &princ_name) != 0)
190 continue;
191 st = krb5_parse_name(context, princ_name, &principal);
192 free(princ_name);
193 if (st)
194 continue;
195
196 if (is_principal_in_realm(ldap_context, principal)) {
197 st = populate_krb5_db_entry(context, ldap_context, ld,
198 ent, principal, &entry);
199 krb5_free_principal(context, principal);
200 if (st)
201 goto cleanup;
202 (*func)(func_arg, &entry);
203 krb5_dbe_free_contents(context, &entry);
204 break;
205 }
206 (void) krb5_free_principal(context, principal);
207 }
208 ldap_value_free(values);
209 }
210 } /* end of for (ent= ... */
211 ldap_msgfree(result);
212 result = NULL;
213 } /* end of for (tree= ... */
214
215 cleanup:
216 if (filter)
217 free (filter);
218
219 for (;ntree; --ntree)
220 if (subtree[ntree-1])
221 free (subtree[ntree-1]);
222 free(subtree);
223
224 ldap_msgfree(result);
225 krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
226 return st;
227 }
228
229
230 /*
231 * delete a principal from the directory.
232 */
233 krb5_error_code
krb5_ldap_delete_principal(krb5_context context,krb5_const_principal searchfor)234 krb5_ldap_delete_principal(krb5_context context,
235 krb5_const_principal searchfor)
236 {
237 char *user=NULL, *DN=NULL, *strval[10] = {NULL};
238 LDAPMod **mods=NULL;
239 LDAP *ld=NULL;
240 int j=0, ptype=0, pcount=0, attrsetmask=0;
241 krb5_error_code st=0;
242 krb5_boolean singleentry=FALSE;
243 kdb5_dal_handle *dal_handle=NULL;
244 krb5_ldap_context *ldap_context=NULL;
245 krb5_ldap_server_handle *ldap_server_handle=NULL;
246 krb5_db_entry *entry = NULL;
247
248 /* Clear the global error string */
249 krb5_clear_error_message(context);
250
251 SETUP_CONTEXT();
252 /* get the principal info */
253 if ((st=krb5_ldap_get_principal(context, searchfor, 0, &entry)))
254 goto cleanup;
255
256 if (((st=krb5_get_princ_type(context, entry, &(ptype))) != 0) ||
257 ((st=krb5_get_attributes_mask(context, entry, &(attrsetmask))) != 0) ||
258 ((st=krb5_get_princ_count(context, entry, &(pcount))) != 0) ||
259 ((st=krb5_get_userdn(context, entry, &(DN))) != 0))
260 goto cleanup;
261
262 if (DN == NULL) {
263 st = EINVAL;
264 k5_setmsg(context, st, _("DN information missing"));
265 goto cleanup;
266 }
267
268 GET_HANDLE();
269
270 if (ptype == KDB_STANDALONE_PRINCIPAL_OBJECT) {
271 st = ldap_delete_ext_s(ld, DN, NULL, NULL);
272 if (st != LDAP_SUCCESS) {
273 st = set_ldap_error (context, st, OP_DEL);
274 goto cleanup;
275 }
276 } else {
277 if (((st=krb5_unparse_name(context, searchfor, &user)) != 0)
278 || ((st=krb5_ldap_unparse_principal_name(user)) != 0))
279 goto cleanup;
280
281 memset(strval, 0, sizeof(strval));
282 strval[0] = user;
283 if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbprincipalname", LDAP_MOD_DELETE,
284 strval)) != 0)
285 goto cleanup;
286
287 singleentry = (pcount == 1) ? TRUE: FALSE;
288 if (singleentry == TRUE) {
289 /*
290 * If the Kerberos user principal to be deleted happens to be the last one associated
291 * with the directory user object, then it is time to delete the other kerberos
292 * specific attributes like krbmaxticketlife, i.e, unkerberize the directory user.
293 * From the attrsetmask value, identify the attributes set on the directory user
294 * object and delete them.
295 * NOTE: krbsecretkey attribute has per principal entries. There can be chances that the
296 * other principals' keys are existing/left-over. So delete all the values.
297 */
298 while (attrsetmask) {
299 if (attrsetmask & 1) {
300 if ((st=krb5_add_str_mem_ldap_mod(&mods, attributes_set[j], LDAP_MOD_DELETE,
301 NULL)) != 0)
302 goto cleanup;
303 }
304 attrsetmask >>= 1;
305 ++j;
306 }
307
308 /* the same should be done with the objectclass attributes */
309 {
310 char *attrvalues[] = {"krbticketpolicyaux", "krbprincipalaux", NULL};
311 /* char *attrvalues[] = {"krbpwdpolicyrefaux", "krbticketpolicyaux", "krbprincipalaux", NULL}; */
312 int p, q, r=0, amask=0;
313
314 if ((st=checkattributevalue(ld, DN, "objectclass", attrvalues, &amask)) != 0)
315 goto cleanup;
316 memset(strval, 0, sizeof(strval));
317 for (p=1, q=0; p<=4; p<<=1, ++q)
318 if (p & amask)
319 strval[r++] = attrvalues[q];
320 strval[r] = NULL;
321 if (r > 0) {
322 if ((st=krb5_add_str_mem_ldap_mod(&mods, "objectclass", LDAP_MOD_DELETE,
323 strval)) != 0)
324 goto cleanup;
325 }
326 }
327 }
328 st=ldap_modify_ext_s(ld, DN, mods, NULL, NULL);
329 if (st != LDAP_SUCCESS) {
330 st = set_ldap_error(context, st, OP_MOD);
331 goto cleanup;
332 }
333 }
334
335 cleanup:
336 if (user)
337 free (user);
338
339 if (DN)
340 free (DN);
341
342 krb5_db_free_principal(context, entry);
343
344 ldap_mods_free(mods, 1);
345 krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
346 return st;
347 }
348
349 /*
350 * Set *res will to 1 if entry is a standalone principal entry, 0 if not. On
351 * error, the value of *res is not defined.
352 */
353 static inline krb5_error_code
is_standalone_principal(krb5_context kcontext,krb5_db_entry * entry,int * res)354 is_standalone_principal(krb5_context kcontext, krb5_db_entry *entry, int *res)
355 {
356 krb5_error_code code;
357
358 code = krb5_get_princ_type(kcontext, entry, res);
359 if (!code)
360 *res = (*res == KDB_STANDALONE_PRINCIPAL_OBJECT) ? 1 : 0;
361 return code;
362 }
363
364 /*
365 * Unparse princ in the format used for LDAP attributes, and set *user to the
366 * result.
367 */
368 static krb5_error_code
unparse_principal_name(krb5_context context,krb5_const_principal princ,char ** user_out)369 unparse_principal_name(krb5_context context, krb5_const_principal princ,
370 char **user_out)
371 {
372 krb5_error_code st;
373 char *luser = NULL;
374
375 *user_out = NULL;
376
377 st = krb5_unparse_name(context, princ, &luser);
378 if (st)
379 goto cleanup;
380
381 st = krb5_ldap_unparse_principal_name(luser);
382 if (st)
383 goto cleanup;
384
385 *user_out = luser;
386 luser = NULL;
387
388 cleanup:
389 free(luser);
390 return st;
391 }
392
393 /*
394 * Rename a principal's rdn.
395 *
396 * NOTE: Not every LDAP ds supports deleting the old rdn. If that is desired,
397 * it will have to be deleted afterwards.
398 */
399 static krb5_error_code
rename_principal_rdn(krb5_context context,LDAP * ld,const char * dn,const char * newprinc,char ** newdn_out)400 rename_principal_rdn(krb5_context context, LDAP *ld, const char *dn,
401 const char *newprinc, char **newdn_out)
402 {
403 int ret;
404 char *newrdn = NULL;
405
406 *newdn_out = NULL;
407
408 ret = asprintf(&newrdn, "krbprincipalname=%s", newprinc);
409 if (ret < 0)
410 return ENOMEM;
411
412 /*
413 * ldap_rename_s takes a deleteoldrdn parameter, but setting it to 1 fails
414 * on 389 Directory Server (as of version 1.3.5.4) if the old RDN value
415 * contains uppercase letters. Instead, change the RDN without deleting
416 * the old value and delete it later.
417 */
418 ret = ldap_rename_s(ld, dn, newrdn, NULL, 0, NULL, NULL);
419 if (ret == -1) {
420 ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &ret);
421 ret = set_ldap_error(context, ret, OP_MOD);
422 goto cleanup;
423 }
424
425 ret = replace_rdn(context, dn, newrdn, newdn_out);
426
427 cleanup:
428 free(newrdn);
429 return ret;
430 }
431
432 /*
433 * Rename a principal.
434 */
435 krb5_error_code
krb5_ldap_rename_principal(krb5_context context,krb5_const_principal source,krb5_const_principal target)436 krb5_ldap_rename_principal(krb5_context context, krb5_const_principal source,
437 krb5_const_principal target)
438 {
439 int is_standalone;
440 krb5_error_code st;
441 char *suser = NULL, *tuser = NULL, *strval[2], *dn = NULL, *newdn = NULL;
442 krb5_db_entry *entry = NULL;
443 krb5_kvno mkvno;
444 struct berval **bersecretkey = NULL;
445 kdb5_dal_handle *dal_handle = NULL;
446 krb5_ldap_context *ldap_context = NULL;
447 krb5_ldap_server_handle *ldap_server_handle = NULL;
448 LDAP *ld = NULL;
449 LDAPMod **mods = NULL;
450
451 /* Clear the global error string */
452 krb5_clear_error_message(context);
453
454 SETUP_CONTEXT();
455 if (ldap_context->lrparams == NULL || ldap_context->container_dn == NULL)
456 return EINVAL;
457
458 /* get ldap handle */
459 GET_HANDLE();
460
461 /* Pass no flags. Principal aliases won't be returned, which is a good
462 * thing since we don't support renaming aliases. */
463 st = krb5_ldap_get_principal(context, source, 0, &entry);
464 if (st)
465 goto cleanup;
466
467 st = is_standalone_principal(context, entry, &is_standalone);
468 if (st)
469 goto cleanup;
470
471 st = krb5_get_userdn(context, entry, &dn);
472 if (st)
473 goto cleanup;
474 if (dn == NULL) {
475 st = EINVAL;
476 k5_setmsg(context, st, _("dn information missing"));
477 goto cleanup;
478 }
479
480 st = unparse_principal_name(context, source, &suser);
481 if (st)
482 goto cleanup;
483 st = unparse_principal_name(context, target, &tuser);
484 if (st)
485 goto cleanup;
486
487 /* Specialize the salt and store it first so that in case of an error the
488 * correct salt will still be used. */
489 st = krb5_dbe_specialize_salt(context, entry);
490 if (st)
491 goto cleanup;
492
493 st = krb5_dbe_lookup_mkvno(context, entry, &mkvno);
494 if (st)
495 goto cleanup;
496
497 bersecretkey = krb5_encode_krbsecretkey(entry->key_data, entry->n_key_data,
498 mkvno);
499 if (bersecretkey == NULL) {
500 st = ENOMEM;
501 goto cleanup;
502 }
503
504 st = krb5_add_ber_mem_ldap_mod(&mods, "krbPrincipalKey",
505 LDAP_MOD_REPLACE | LDAP_MOD_BVALUES,
506 bersecretkey);
507 if (st != 0)
508 goto cleanup;
509
510 /* Update the principal. */
511 st = krb5_ldap_modify_ext(context, ld, dn, mods, OP_MOD);
512 if (st)
513 goto cleanup;
514 ldap_mods_free(mods, 1);
515 mods = NULL;
516
517 /* If this is a standalone principal, we want to rename the DN of the LDAP
518 * entry. If not, we will modify the entry without changing its DN. */
519 if (is_standalone) {
520 st = rename_principal_rdn(context, ld, dn, tuser, &newdn);
521 if (st)
522 goto cleanup;
523 free(dn);
524 dn = newdn;
525 newdn = NULL;
526 }
527
528 /* There can be more than one krbPrincipalName, so we have to delete
529 * the old one and add the new one. */
530 strval[0] = suser;
531 strval[1] = NULL;
532 st = krb5_add_str_mem_ldap_mod(&mods, "krbPrincipalName", LDAP_MOD_DELETE,
533 strval);
534 if (st)
535 goto cleanup;
536
537 strval[0] = tuser;
538 strval[1] = NULL;
539 if (!is_standalone) {
540 st = krb5_add_str_mem_ldap_mod(&mods, "krbPrincipalName", LDAP_MOD_ADD,
541 strval);
542 if (st)
543 goto cleanup;
544 }
545
546 st = krb5_add_str_mem_ldap_mod(&mods, "krbCanonicalName", LDAP_MOD_REPLACE,
547 strval);
548 if (st)
549 goto cleanup;
550
551 /* Update the principal. */
552 st = krb5_ldap_modify_ext(context, ld, dn, mods, OP_MOD);
553 if (st)
554 goto cleanup;
555
556 cleanup:
557 free(dn);
558 free(suser);
559 free(tuser);
560 free_berdata(bersecretkey);
561 krb5_db_free_principal(context, entry);
562 ldap_mods_free(mods, 1);
563 krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
564 return st;
565 }
566
567 /*
568 * Function: krb5_ldap_unparse_principal_name
569 *
570 * Purpose: Removes '\\' that comes before every occurrence of '@'
571 * in the principal name component.
572 *
573 * Arguments:
574 * user_name (input/output) Principal name
575 *
576 */
577
578 krb5_error_code
krb5_ldap_unparse_principal_name(char * user_name)579 krb5_ldap_unparse_principal_name(char *user_name)
580 {
581 char *in, *out;
582
583 out = user_name;
584 for (in = user_name; *in; in++) {
585 if (*in == '\\' && *(in + 1) == '@')
586 continue;
587 *out++ = *in;
588 }
589 *out = '\0';
590
591 return 0;
592 }
593
594
595 /*
596 * Function: krb5_ldap_parse_principal_name
597 *
598 * Purpose: Inserts '\\' before every occurrence of '@'
599 * in the principal name component.
600 *
601 * Arguments:
602 * i_princ_name (input) Principal name without '\\'
603 * o_princ_name (output) Principal name with '\\'
604 *
605 * Note: The caller has to free the memory allocated for o_princ_name.
606 */
607
608 krb5_error_code
krb5_ldap_parse_principal_name(char * i_princ_name,char ** o_princ_name)609 krb5_ldap_parse_principal_name(char *i_princ_name, char **o_princ_name)
610 {
611 const char *at_rlm_name, *p;
612 struct k5buf buf;
613
614 at_rlm_name = strrchr(i_princ_name, '@');
615 if (!at_rlm_name) {
616 *o_princ_name = strdup(i_princ_name);
617 } else {
618 k5_buf_init_dynamic(&buf);
619 for (p = i_princ_name; p < at_rlm_name; p++) {
620 if (*p == '@')
621 k5_buf_add(&buf, "\\");
622 k5_buf_add_len(&buf, p, 1);
623 }
624 k5_buf_add(&buf, at_rlm_name);
625 *o_princ_name = k5_buf_cstring(&buf);
626 }
627 return (*o_princ_name == NULL) ? ENOMEM : 0;
628 }
629