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