xref: /freebsd/crypto/krb5/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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