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 static krb5_error_code
iterate_entry(krb5_context context,krb5_ldap_context * ldap_context,LDAP * ld,LDAPMessage * ent,krb5_error_code (* func)(krb5_pointer,krb5_db_entry *),krb5_pointer func_arg)132 iterate_entry(krb5_context context, krb5_ldap_context *ldap_context,
133 LDAP *ld, LDAPMessage *ent,
134 krb5_error_code (*func)(krb5_pointer, krb5_db_entry *),
135 krb5_pointer func_arg)
136 {
137 krb5_error_code ret = 0;
138 krb5_principal cprinc = NULL, nprinc = NULL;
139 krb5_db_entry entry = { 0 }, *entptr;
140 char **canon = NULL, **names = NULL, **list;
141 size_t i;
142
143 canon = ldap_get_values(ld, ent, "krbCanonicalName");
144 names = ldap_get_values(ld, ent, "krbPrincipalName");
145 if (canon == NULL && names == NULL)
146 return 0;
147
148 /* Output an entry for the canonical name if one is given. Otherwise
149 * output an entry for the first name within the realm. */
150 list = (canon != NULL) ? canon : names;
151 for (i = 0; list[i] != NULL; i++) {
152 krb5_free_principal(context, nprinc);
153 nprinc = NULL;
154 ret = krb5_ldap_parse_name(context, list[i], &nprinc);
155 if (ret)
156 goto cleanup;
157
158 if (is_principal_in_realm(ldap_context, nprinc)) {
159 ret = populate_krb5_db_entry(context, ldap_context, ld, ent,
160 nprinc, &entry);
161 if (ret)
162 goto cleanup;
163 ret = (*func)(func_arg, &entry);
164 krb5_dbe_free_contents(context, &entry);
165 if (ret)
166 goto cleanup;
167 break;
168 }
169 }
170
171 /* Output alias entries for each non-canonical name. */
172 if (canon != NULL && names != NULL) {
173 ret = krb5_ldap_parse_name(context, canon[0], &cprinc);
174 if (ret)
175 goto cleanup;
176 for (i = 0; names[i] != NULL; i++) {
177 if (strcmp(names[i], canon[0]) == 0)
178 continue;
179 krb5_free_principal(context, nprinc);
180 nprinc = NULL;
181 ret = krb5_ldap_parse_name(context, names[i], &nprinc);
182 if (ret)
183 goto cleanup;
184 ret = krb5_dbe_make_alias_entry(context, nprinc, cprinc, &entptr);
185 if (ret)
186 goto cleanup;
187 ret = (*func)(func_arg, entptr);
188 krb5_db_free_principal(context, entptr);
189 if (ret)
190 goto cleanup;
191 }
192 }
193
194 cleanup:
195 krb5_free_principal(context, cprinc);
196 krb5_free_principal(context, nprinc);
197 ldap_value_free(canon);
198 ldap_value_free(names);
199 return ret;
200 }
201
202 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)203 krb5_ldap_iterate(krb5_context context, char *match_expr,
204 krb5_error_code (*func)(krb5_pointer, krb5_db_entry *),
205 krb5_pointer func_arg, krb5_flags iterflags)
206 {
207 krb5_db_entry entry;
208 char **subtree=NULL, *realm=NULL, *filter=NULL;
209 size_t tree=0, ntree=1;
210 krb5_error_code st=0, tempst=0;
211 LDAP *ld=NULL;
212 LDAPMessage *result=NULL, *ent=NULL;
213 kdb5_dal_handle *dal_handle=NULL;
214 krb5_ldap_context *ldap_context=NULL;
215 krb5_ldap_server_handle *ldap_server_handle=NULL;
216 char *default_match_expr = "*";
217
218 /* Clear the global error string */
219 krb5_clear_error_message(context);
220
221 memset(&entry, 0, sizeof(krb5_db_entry));
222 SETUP_CONTEXT();
223
224 realm = ldap_context->lrparams->realm_name;
225 if (realm == NULL) {
226 realm = context->default_realm;
227 if (realm == NULL) {
228 st = EINVAL;
229 k5_setmsg(context, st, _("Default realm not set"));
230 goto cleanup;
231 }
232 }
233
234 /*
235 * If no match_expr then iterate through all krb princs like the db2 plugin
236 */
237 if (match_expr == NULL)
238 match_expr = default_match_expr;
239
240 if (asprintf(&filter, FILTER"%s))", match_expr) < 0)
241 filter = NULL;
242 CHECK_NULL(filter);
243
244 if ((st = krb5_get_subtree_info(ldap_context, &subtree, &ntree)) != 0)
245 goto cleanup;
246
247 GET_HANDLE();
248
249 for (tree=0; tree < ntree; ++tree) {
250
251 LDAP_SEARCH(subtree[tree], ldap_context->lrparams->search_scope, filter, principal_attributes);
252 for (ent=ldap_first_entry(ld, result); ent != NULL; ent=ldap_next_entry(ld, ent)) {
253 st = iterate_entry(context, ldap_context, ld, ent, func, func_arg);
254 if (st)
255 goto cleanup;
256 }
257 ldap_msgfree(result);
258 result = NULL;
259 } /* end of for (tree= ... */
260
261 cleanup:
262 if (filter)
263 free (filter);
264
265 for (;ntree; --ntree)
266 if (subtree[ntree-1])
267 free (subtree[ntree-1]);
268 free(subtree);
269
270 ldap_msgfree(result);
271 krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
272 return st;
273 }
274
275
276 /*
277 * delete a principal from the directory.
278 */
279 krb5_error_code
krb5_ldap_delete_principal(krb5_context context,krb5_const_principal searchfor)280 krb5_ldap_delete_principal(krb5_context context,
281 krb5_const_principal searchfor)
282 {
283 char *user=NULL, *DN=NULL, *strval[10] = {NULL};
284 LDAPMod **mods=NULL;
285 LDAP *ld=NULL;
286 size_t j=0;
287 int ptype=0, pcount=0, attrsetmask=0;
288 krb5_error_code st=0;
289 krb5_boolean singleentry=FALSE;
290 kdb5_dal_handle *dal_handle=NULL;
291 krb5_ldap_context *ldap_context=NULL;
292 krb5_ldap_server_handle *ldap_server_handle=NULL;
293 krb5_db_entry *entry = NULL;
294
295 /* Clear the global error string */
296 krb5_clear_error_message(context);
297
298 SETUP_CONTEXT();
299 /* get the principal info */
300 if ((st=krb5_ldap_get_principal(context, searchfor, 0, &entry)))
301 goto cleanup;
302
303 if (((st=krb5_get_princ_type(context, entry, &(ptype))) != 0) ||
304 ((st=krb5_get_attributes_mask(context, entry, &(attrsetmask))) != 0) ||
305 ((st=krb5_get_princ_count(context, entry, &(pcount))) != 0) ||
306 ((st=krb5_get_userdn(context, entry, &(DN))) != 0))
307 goto cleanup;
308
309 if (DN == NULL) {
310 st = EINVAL;
311 k5_setmsg(context, st, _("DN information missing"));
312 goto cleanup;
313 }
314
315 GET_HANDLE();
316
317 if (ptype == KDB_STANDALONE_PRINCIPAL_OBJECT &&
318 (pcount == 1 ||
319 krb5_principal_compare(context, searchfor, entry->princ))) {
320 st = ldap_delete_ext_s(ld, DN, NULL, NULL);
321 if (st != LDAP_SUCCESS) {
322 st = set_ldap_error (context, st, OP_DEL);
323 goto cleanup;
324 }
325 } else {
326 st = krb5_ldap_unparse_name(context, searchfor, &user);
327 if (st)
328 goto cleanup;
329
330 memset(strval, 0, sizeof(strval));
331 strval[0] = user;
332 if ((st=krb5_add_str_mem_ldap_mod(&mods, "krbprincipalname", LDAP_MOD_DELETE,
333 strval)) != 0)
334 goto cleanup;
335
336 singleentry = (pcount == 1) ? TRUE: FALSE;
337 if (singleentry == TRUE) {
338 /*
339 * If the Kerberos user principal to be deleted happens to be the last one associated
340 * with the directory user object, then it is time to delete the other kerberos
341 * specific attributes like krbmaxticketlife, i.e, unkerberize the directory user.
342 * From the attrsetmask value, identify the attributes set on the directory user
343 * object and delete them.
344 * NOTE: krbsecretkey attribute has per principal entries. There can be chances that the
345 * other principals' keys are existing/left-over. So delete all the values.
346 */
347 while (attrsetmask) {
348 if (attrsetmask & 1) {
349 if ((st=krb5_add_str_mem_ldap_mod(&mods, attributes_set[j], LDAP_MOD_DELETE,
350 NULL)) != 0)
351 goto cleanup;
352 }
353 attrsetmask >>= 1;
354 ++j;
355 }
356
357 /* the same should be done with the objectclass attributes */
358 {
359 char *attrvalues[] = {"krbticketpolicyaux", "krbprincipalaux", NULL};
360 /* char *attrvalues[] = {"krbpwdpolicyrefaux", "krbticketpolicyaux", "krbprincipalaux", NULL}; */
361 int p, q, r=0, amask=0;
362
363 if ((st=checkattributevalue(ld, DN, "objectclass", attrvalues, &amask)) != 0)
364 goto cleanup;
365 memset(strval, 0, sizeof(strval));
366 for (p=1, q=0; p<=4; p<<=1, ++q)
367 if (p & amask)
368 strval[r++] = attrvalues[q];
369 strval[r] = NULL;
370 if (r > 0) {
371 if ((st=krb5_add_str_mem_ldap_mod(&mods, "objectclass", LDAP_MOD_DELETE,
372 strval)) != 0)
373 goto cleanup;
374 }
375 }
376 }
377 st=ldap_modify_ext_s(ld, DN, mods, NULL, NULL);
378 if (st != LDAP_SUCCESS) {
379 st = set_ldap_error(context, st, OP_MOD);
380 goto cleanup;
381 }
382 }
383
384 cleanup:
385 if (user)
386 free (user);
387
388 if (DN)
389 free (DN);
390
391 krb5_db_free_principal(context, entry);
392
393 ldap_mods_free(mods, 1);
394 krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
395 return st;
396 }
397
398 /*
399 * Set *res will to 1 if entry is a standalone principal entry, 0 if not. On
400 * error, the value of *res is not defined.
401 */
402 static inline krb5_error_code
is_standalone_principal(krb5_context kcontext,krb5_db_entry * entry,int * res)403 is_standalone_principal(krb5_context kcontext, krb5_db_entry *entry, int *res)
404 {
405 krb5_error_code code;
406
407 code = krb5_get_princ_type(kcontext, entry, res);
408 if (!code)
409 *res = (*res == KDB_STANDALONE_PRINCIPAL_OBJECT) ? 1 : 0;
410 return code;
411 }
412
413 /*
414 * Rename a principal's rdn.
415 *
416 * NOTE: Not every LDAP ds supports deleting the old rdn. If that is desired,
417 * it will have to be deleted afterwards.
418 */
419 static krb5_error_code
rename_principal_rdn(krb5_context context,LDAP * ld,const char * dn,const char * newprinc,char ** newdn_out)420 rename_principal_rdn(krb5_context context, LDAP *ld, const char *dn,
421 const char *newprinc, char **newdn_out)
422 {
423 int ret;
424 char *newrdn = NULL;
425
426 *newdn_out = NULL;
427
428 ret = asprintf(&newrdn, "krbprincipalname=%s", newprinc);
429 if (ret < 0)
430 return ENOMEM;
431
432 /*
433 * ldap_rename_s takes a deleteoldrdn parameter, but setting it to 1 fails
434 * on 389 Directory Server (as of version 1.3.5.4) if the old RDN value
435 * contains uppercase letters. Instead, change the RDN without deleting
436 * the old value and delete it later.
437 */
438 ret = ldap_rename_s(ld, dn, newrdn, NULL, 0, NULL, NULL);
439 if (ret == -1) {
440 ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &ret);
441 ret = set_ldap_error(context, ret, OP_MOD);
442 goto cleanup;
443 }
444
445 ret = replace_rdn(context, dn, newrdn, newdn_out);
446
447 cleanup:
448 free(newrdn);
449 return ret;
450 }
451
452 /*
453 * Rename a principal.
454 */
455 krb5_error_code
krb5_ldap_rename_principal(krb5_context context,krb5_const_principal source,krb5_const_principal target)456 krb5_ldap_rename_principal(krb5_context context, krb5_const_principal source,
457 krb5_const_principal target)
458 {
459 int is_standalone;
460 krb5_error_code st;
461 char *suser = NULL, *tuser = NULL, *strval[2], *dn = NULL, *newdn = NULL;
462 krb5_db_entry *entry = NULL;
463 krb5_kvno mkvno;
464 struct berval **bersecretkey = NULL;
465 kdb5_dal_handle *dal_handle = NULL;
466 krb5_ldap_context *ldap_context = NULL;
467 krb5_ldap_server_handle *ldap_server_handle = NULL;
468 LDAP *ld = NULL;
469 LDAPMod **mods = NULL;
470
471 /* Clear the global error string */
472 krb5_clear_error_message(context);
473
474 SETUP_CONTEXT();
475 if (ldap_context->lrparams == NULL || ldap_context->container_dn == NULL)
476 return EINVAL;
477
478 /* get ldap handle */
479 GET_HANDLE();
480
481 /* Pass no flags. Principal aliases won't be returned, which is a good
482 * thing since we don't support renaming aliases. */
483 st = krb5_ldap_get_principal(context, source, 0, &entry);
484 if (st)
485 goto cleanup;
486
487 st = is_standalone_principal(context, entry, &is_standalone);
488 if (st)
489 goto cleanup;
490
491 st = krb5_get_userdn(context, entry, &dn);
492 if (st)
493 goto cleanup;
494 if (dn == NULL) {
495 st = EINVAL;
496 k5_setmsg(context, st, _("dn information missing"));
497 goto cleanup;
498 }
499
500 st = krb5_ldap_unparse_name(context, source, &suser);
501 if (st)
502 goto cleanup;
503 st = krb5_ldap_unparse_name(context, target, &tuser);
504 if (st)
505 goto cleanup;
506
507 /* Specialize the salt and store it first so that in case of an error the
508 * correct salt will still be used. */
509 st = krb5_dbe_specialize_salt(context, entry);
510 if (st)
511 goto cleanup;
512
513 st = krb5_dbe_lookup_mkvno(context, entry, &mkvno);
514 if (st)
515 goto cleanup;
516
517 bersecretkey = krb5_encode_krbsecretkey(entry->key_data, entry->n_key_data,
518 mkvno);
519 if (bersecretkey == NULL) {
520 st = ENOMEM;
521 goto cleanup;
522 }
523
524 st = krb5_add_ber_mem_ldap_mod(&mods, "krbPrincipalKey",
525 LDAP_MOD_REPLACE | LDAP_MOD_BVALUES,
526 bersecretkey);
527 if (st != 0)
528 goto cleanup;
529
530 /* Update the principal. */
531 st = krb5_ldap_modify_ext(context, ld, dn, mods, OP_MOD);
532 if (st)
533 goto cleanup;
534 ldap_mods_free(mods, 1);
535 mods = NULL;
536
537 /* If this is a standalone principal, we want to rename the DN of the LDAP
538 * entry. If not, we will modify the entry without changing its DN. */
539 if (is_standalone) {
540 st = rename_principal_rdn(context, ld, dn, tuser, &newdn);
541 if (st)
542 goto cleanup;
543 free(dn);
544 dn = newdn;
545 newdn = NULL;
546 }
547
548 /* There can be more than one krbPrincipalName, so we have to delete
549 * the old one and add the new one. */
550 strval[0] = suser;
551 strval[1] = NULL;
552 st = krb5_add_str_mem_ldap_mod(&mods, "krbPrincipalName", LDAP_MOD_DELETE,
553 strval);
554 if (st)
555 goto cleanup;
556
557 strval[0] = tuser;
558 strval[1] = NULL;
559 if (!is_standalone) {
560 st = krb5_add_str_mem_ldap_mod(&mods, "krbPrincipalName", LDAP_MOD_ADD,
561 strval);
562 if (st)
563 goto cleanup;
564 }
565
566 st = krb5_add_str_mem_ldap_mod(&mods, "krbCanonicalName", LDAP_MOD_REPLACE,
567 strval);
568 if (st)
569 goto cleanup;
570
571 /* Update the principal. */
572 st = krb5_ldap_modify_ext(context, ld, dn, mods, OP_MOD);
573 if (st)
574 goto cleanup;
575
576 cleanup:
577 free(dn);
578 free(suser);
579 free(tuser);
580 free_berdata(bersecretkey);
581 krb5_db_free_principal(context, entry);
582 ldap_mods_free(mods, 1);
583 krb5_ldap_put_handle_to_pool(ldap_context, ldap_server_handle);
584 return st;
585 }
586
587 /* Unparse princ in the format used for krb5 principal names within LDAP
588 * attributes. */
589 krb5_error_code
krb5_ldap_unparse_name(krb5_context context,krb5_const_principal princ,char ** user_out)590 krb5_ldap_unparse_name(krb5_context context, krb5_const_principal princ,
591 char **user_out)
592 {
593 krb5_error_code ret;
594 char *p, *q;
595
596 ret = krb5_unparse_name(context, princ, user_out);
597 if (ret)
598 return ret;
599
600 /* Remove backslashes preceding at-signs in the unparsed string. */
601 for (q = p = *user_out; *p != '\0'; p++) {
602 if (*p == '\\' && *(p + 1) == '@')
603 continue;
604 *q++ = *p;
605 }
606 *q = '\0';
607
608 return 0;
609 }
610
611 /* Parse username in the format used for krb5 principal names within LDAP
612 * attributes. */
613 krb5_error_code
krb5_ldap_parse_name(krb5_context context,const char * username,krb5_principal * out)614 krb5_ldap_parse_name(krb5_context context, const char *username,
615 krb5_principal *out)
616 {
617 krb5_error_code ret;
618 const char *at_realm, *p;
619 char *princstr;
620 struct k5buf buf;
621
622 *out = NULL;
623
624 /* Make a copy of username, inserting a backslash before each '@'
625 * before the last one. */
626 at_realm = strrchr(username, '@');
627 if (at_realm == NULL) {
628 princstr = strdup(username);
629 } else {
630 k5_buf_init_dynamic(&buf);
631 for (p = username; p < at_realm; p++) {
632 if (*p == '@')
633 k5_buf_add(&buf, "\\");
634 k5_buf_add_len(&buf, p, 1);
635 }
636 k5_buf_add(&buf, at_realm);
637 princstr = k5_buf_cstring(&buf);
638 }
639
640 if (princstr == NULL)
641 return ENOMEM;
642
643 ret = krb5_parse_name(context, princstr, out);
644 free(princstr);
645 return ret;
646 }
647