Home | History | Annotate | Line # | Download | only in krb5
keytab.c revision 1.3
      1 /*	$NetBSD: keytab.c,v 1.3 2023/06/19 21:41:44 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1997 - 2005 Kungliga Tekniska Hgskolan
      5  * (Royal Institute of Technology, Stockholm, Sweden).
      6  * All rights reserved.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  *
     12  * 1. Redistributions of source code must retain the above copyright
     13  *    notice, this list of conditions and the following disclaimer.
     14  *
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * 3. Neither the name of the Institute nor the names of its contributors
     20  *    may be used to endorse or promote products derived from this software
     21  *    without specific prior written permission.
     22  *
     23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
     24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
     27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     33  * SUCH DAMAGE.
     34  */
     35 
     36 #include "krb5_locl.h"
     37 
     38 /**
     39  * @page krb5_keytab_intro The keytab handing functions
     40  * @section section_krb5_keytab Kerberos Keytabs
     41  *
     42  * See the library functions here: @ref krb5_keytab
     43  *
     44  * Keytabs are long term key storage for servers, their equvalment of
     45  * password files.
     46  *
     47  * Normally the only function that useful for server are to specify
     48  * what keytab to use to other core functions like krb5_rd_req()
     49  * krb5_kt_resolve(), and krb5_kt_close().
     50  *
     51  * @subsection krb5_keytab_names Keytab names
     52  *
     53  * A keytab name is on the form type:residual. The residual part is
     54  * specific to each keytab-type.
     55  *
     56  * When a keytab-name is resolved, the type is matched with an internal
     57  * list of keytab types. If there is no matching keytab type,
     58  * the default keytab is used. The current default type is FILE.
     59  *
     60  * The default value can be changed in the configuration file
     61  * /etc/krb5.conf by setting the variable
     62  * [defaults]default_keytab_name.
     63  *
     64  * The keytab types that are implemented in Heimdal are:
     65  * - file
     66  *   store the keytab in a file, the type's name is FILE .  The
     67  *   residual part is a filename. For compatibility with other
     68  *   Kerberos implemtation WRFILE and JAVA14 is also accepted.  WRFILE
     69  *   has the same format as FILE. JAVA14 have a format that is
     70  *   compatible with older versions of MIT kerberos and SUN's Java
     71  *   based installation.  They store a truncted kvno, so when the knvo
     72  *   excess 255, they are truncted in this format.
     73  *
     74  * - keytab
     75  *   store the keytab in a AFS keyfile (usually /usr/afs/etc/KeyFile ),
     76  *   the type's name is AFSKEYFILE. The residual part is a filename.
     77  *
     78  * - memory
     79  *   The keytab is stored in a memory segment. This allows sensitive
     80  *   and/or temporary data not to be stored on disk. The type's name
     81  *   is MEMORY. Each MEMORY keytab is referenced counted by and
     82  *   opened by the residual name, so two handles can point to the
     83  *   same memory area.  When the last user closes using krb5_kt_close()
     84  *   the keytab, the keys in they keytab is memset() to zero and freed
     85  *   and can no longer be looked up by name.
     86  *
     87  *
     88  * @subsection krb5_keytab_example Keytab example
     89  *
     90  *  This is a minimalistic version of ktutil.
     91  *
     92  * @code
     93 int
     94 main (int argc, char **argv)
     95 {
     96     krb5_context context;
     97     krb5_keytab keytab;
     98     krb5_kt_cursor cursor;
     99     krb5_keytab_entry entry;
    100     krb5_error_code ret;
    101     char *principal;
    102 
    103     if (krb5_init_context (&context) != 0)
    104 	errx(1, "krb5_context");
    105 
    106     ret = krb5_kt_default (context, &keytab);
    107     if (ret)
    108 	krb5_err(context, 1, ret, "krb5_kt_default");
    109 
    110     ret = krb5_kt_start_seq_get(context, keytab, &cursor);
    111     if (ret)
    112 	krb5_err(context, 1, ret, "krb5_kt_start_seq_get");
    113     while((ret = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0){
    114 	krb5_unparse_name(context, entry.principal, &principal);
    115 	printf("principal: %s\n", principal);
    116 	free(principal);
    117 	krb5_kt_free_entry(context, &entry);
    118     }
    119     ret = krb5_kt_end_seq_get(context, keytab, &cursor);
    120     if (ret)
    121 	krb5_err(context, 1, ret, "krb5_kt_end_seq_get");
    122     ret = krb5_kt_close(context, keytab);
    123     if (ret)
    124 	krb5_err(context, 1, ret, "krb5_kt_close");
    125     krb5_free_context(context);
    126     return 0;
    127 }
    128  * @endcode
    129  *
    130  */
    131 
    132 
    133 /**
    134  * Register a new keytab backend.
    135  *
    136  * @param context a Keberos context.
    137  * @param ops a backend to register.
    138  *
    139  * @return Return an error code or 0, see krb5_get_error_message().
    140  *
    141  * @ingroup krb5_keytab
    142  */
    143 
    144 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    145 krb5_kt_register(krb5_context context,
    146 		 const krb5_kt_ops *ops)
    147 {
    148     struct krb5_keytab_data *tmp;
    149 
    150     if (strlen(ops->prefix) > KRB5_KT_PREFIX_MAX_LEN - 1) {
    151 	krb5_set_error_message(context, KRB5_KT_BADNAME,
    152 			       N_("can't register cache type, prefix too long", ""));
    153 	return KRB5_KT_BADNAME;
    154     }
    155 
    156     tmp = realloc(context->kt_types,
    157 		  (context->num_kt_types + 1) * sizeof(*context->kt_types));
    158     if(tmp == NULL)
    159 	return krb5_enomem(context);
    160     memcpy(&tmp[context->num_kt_types], ops,
    161 	   sizeof(tmp[context->num_kt_types]));
    162     context->kt_types = tmp;
    163     context->num_kt_types++;
    164     return 0;
    165 }
    166 
    167 static const char *
    168 keytab_name(const char *name, const char **type, size_t *type_len)
    169 {
    170     const char *residual;
    171 
    172     residual = strchr(name, ':');
    173 
    174     if (residual == NULL ||
    175 	ISPATHSEP(name[0])
    176 #ifdef _WIN32
    177         /* Avoid treating <drive>:<path> as a keytab type
    178          * specification */
    179         || name + 1 == residual
    180 #endif
    181         ) {
    182 
    183         *type = "FILE";
    184         *type_len = strlen(*type);
    185         residual = name;
    186     } else {
    187         *type = name;
    188         *type_len = residual - name;
    189         residual++;
    190     }
    191 
    192     return residual;
    193 }
    194 
    195 /**
    196  * Resolve the keytab name (of the form `type:residual') in `name'
    197  * into a keytab in `id'.
    198  *
    199  * @param context a Keberos context.
    200  * @param name name to resolve
    201  * @param id resulting keytab, free with krb5_kt_close().
    202  *
    203  * @return Return an error code or 0, see krb5_get_error_message().
    204  *
    205  * @ingroup krb5_keytab
    206  */
    207 
    208 
    209 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    210 krb5_kt_resolve(krb5_context context,
    211 		const char *name,
    212 		krb5_keytab *id)
    213 {
    214     krb5_keytab k;
    215     int i;
    216     const char *type, *residual;
    217     size_t type_len;
    218     krb5_error_code ret;
    219 
    220     residual = keytab_name(name, &type, &type_len);
    221 
    222     for(i = 0; i < context->num_kt_types; i++) {
    223 	if(strncasecmp(type, context->kt_types[i].prefix, type_len) == 0)
    224 	    break;
    225     }
    226     if(i == context->num_kt_types) {
    227 	krb5_set_error_message(context, KRB5_KT_UNKNOWN_TYPE,
    228 			       N_("unknown keytab type %.*s", "type"),
    229 			       (int)type_len, type);
    230 	return KRB5_KT_UNKNOWN_TYPE;
    231     }
    232 
    233     k = malloc (sizeof(*k));
    234     if (k == NULL)
    235 	return krb5_enomem(context);
    236     memcpy(k, &context->kt_types[i], sizeof(*k));
    237     k->data = NULL;
    238     ret = (*k->resolve)(context, residual, k);
    239     if(ret) {
    240 	free(k);
    241 	k = NULL;
    242     }
    243     *id = k;
    244     return ret;
    245 }
    246 
    247 /*
    248  * Default ktname from context with possible environment
    249  * override
    250  */
    251 static const char *default_ktname(krb5_context context)
    252 {
    253     const char *tmp = NULL;
    254 
    255     if(!issuid())
    256 	tmp = getenv("KRB5_KTNAME");
    257     if(tmp != NULL)
    258 	return tmp;
    259     return context->default_keytab;
    260 }
    261 
    262 /**
    263  * copy the name of the default keytab into `name'.
    264  *
    265  * @param context a Keberos context.
    266  * @param name buffer where the name will be written
    267  * @param namesize length of name
    268  *
    269  * @return Return an error code or 0, see krb5_get_error_message().
    270  *
    271  * @ingroup krb5_keytab
    272  */
    273 
    274 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    275 krb5_kt_default_name(krb5_context context, char *name, size_t namesize)
    276 {
    277     if (strlcpy (name, default_ktname(context), namesize) >= namesize) {
    278 	krb5_clear_error_message (context);
    279 	return KRB5_CONFIG_NOTENUFSPACE;
    280     }
    281     return 0;
    282 }
    283 
    284 /**
    285  * Copy the name of the default modify keytab into `name'.
    286  *
    287  * @param context a Keberos context.
    288  * @param name buffer where the name will be written
    289  * @param namesize length of name
    290  *
    291  * @return Return an error code or 0, see krb5_get_error_message().
    292  *
    293  * @ingroup krb5_keytab
    294  */
    295 
    296 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    297 krb5_kt_default_modify_name(krb5_context context, char *name, size_t namesize)
    298 {
    299     const char *kt;
    300 
    301     if(context->default_keytab_modify == NULL) {
    302 	kt = default_ktname(context);
    303 
    304 	if (strncasecmp(kt, "ANY:", 4) == 0) {
    305 	    size_t len = strcspn(kt + 4, ",");
    306 	    if (len >= namesize) {
    307 		krb5_clear_error_message(context);
    308 		return KRB5_CONFIG_NOTENUFSPACE;
    309 	    }
    310 	    strlcpy(name, kt + 4, namesize);
    311 	    name[len] = '\0';
    312 	    return 0;
    313 	}
    314     } else
    315 	kt = context->default_keytab_modify;
    316     if (strlcpy (name, kt, namesize) >= namesize) {
    317 	krb5_clear_error_message (context);
    318 	return KRB5_CONFIG_NOTENUFSPACE;
    319     }
    320     return 0;
    321 }
    322 
    323 /**
    324  * Set `id' to the default keytab.
    325  *
    326  * @param context a Keberos context.
    327  * @param id the new default keytab.
    328  *
    329  * @return Return an error code or 0, see krb5_get_error_message().
    330  *
    331  * @ingroup krb5_keytab
    332  */
    333 
    334 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    335 krb5_kt_default(krb5_context context, krb5_keytab *id)
    336 {
    337     return krb5_kt_resolve (context, default_ktname(context), id);
    338 }
    339 
    340 /**
    341  * Read the key identified by `(principal, vno, enctype)' from the
    342  * keytab in `keyprocarg' (the default if == NULL) into `*key'.
    343  *
    344  * @param context a Keberos context.
    345  * @param keyprocarg
    346  * @param principal
    347  * @param vno
    348  * @param enctype
    349  * @param key
    350  *
    351  * @return Return an error code or 0, see krb5_get_error_message().
    352  *
    353  * @ingroup krb5_keytab
    354  */
    355 
    356 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    357 krb5_kt_read_service_key(krb5_context context,
    358 			 krb5_pointer keyprocarg,
    359 			 krb5_principal principal,
    360 			 krb5_kvno vno,
    361 			 krb5_enctype enctype,
    362 			 krb5_keyblock **key)
    363 {
    364     krb5_keytab keytab = NULL; /* Quiet lint */
    365     krb5_keytab_entry entry;
    366     krb5_error_code ret;
    367 
    368     memset(&entry, 0, sizeof(entry));
    369     if (keyprocarg)
    370 	ret = krb5_kt_resolve (context, keyprocarg, &keytab);
    371     else
    372 	ret = krb5_kt_default (context, &keytab);
    373 
    374     if (ret)
    375 	return ret;
    376 
    377     ret = krb5_kt_get_entry (context, keytab, principal, vno, enctype, &entry);
    378     if (ret == 0) {
    379         ret = krb5_copy_keyblock (context, &entry.keyblock, key);
    380         krb5_kt_free_entry(context, &entry);
    381     }
    382     krb5_kt_close (context, keytab);
    383     return ret;
    384 }
    385 
    386 /**
    387  * Return the type of the `keytab' in the string `prefix of length
    388  * `prefixsize'.
    389  *
    390  * @param context a Keberos context.
    391  * @param keytab the keytab to get the prefix for
    392  * @param prefix prefix buffer
    393  * @param prefixsize length of prefix buffer
    394  *
    395  * @return Return an error code or 0, see krb5_get_error_message().
    396  *
    397  * @ingroup krb5_keytab
    398  */
    399 
    400 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    401 krb5_kt_get_type(krb5_context context,
    402 		 krb5_keytab keytab,
    403 		 char *prefix,
    404 		 size_t prefixsize)
    405 {
    406     strlcpy(prefix, keytab->prefix, prefixsize);
    407     return 0;
    408 }
    409 
    410 /**
    411  * Retrieve the name of the keytab `keytab' into `name', `namesize'
    412  *
    413  * @param context a Keberos context.
    414  * @param keytab the keytab to get the name for.
    415  * @param name name buffer.
    416  * @param namesize size of name buffer.
    417  *
    418  * @return Return an error code or 0, see krb5_get_error_message().
    419  *
    420  * @ingroup krb5_keytab
    421  */
    422 
    423 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    424 krb5_kt_get_name(krb5_context context,
    425 		 krb5_keytab keytab,
    426 		 char *name,
    427 		 size_t namesize)
    428 {
    429     return (*keytab->get_name)(context, keytab, name, namesize);
    430 }
    431 
    432 /**
    433  * Retrieve the full name of the keytab `keytab' and store the name in
    434  * `str'.
    435  *
    436  * @param context a Keberos context.
    437  * @param keytab keytab to get name for.
    438  * @param str the name of the keytab name, usee krb5_xfree() to free
    439  *        the string.  On error, *str is set to NULL.
    440  *
    441  * @return Return an error code or 0, see krb5_get_error_message().
    442  *
    443  * @ingroup krb5_keytab
    444  */
    445 
    446 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    447 krb5_kt_get_full_name(krb5_context context,
    448 		      krb5_keytab keytab,
    449 		      char **str)
    450 {
    451     char type[KRB5_KT_PREFIX_MAX_LEN];
    452     char name[MAXPATHLEN];
    453     krb5_error_code ret;
    454 
    455     *str = NULL;
    456 
    457     ret = krb5_kt_get_type(context, keytab, type, sizeof(type));
    458     if (ret)
    459 	return ret;
    460 
    461     ret = krb5_kt_get_name(context, keytab, name, sizeof(name));
    462     if (ret)
    463 	return ret;
    464 
    465     if (asprintf(str, "%s:%s", type, name) == -1) {
    466 	*str = NULL;
    467 	return krb5_enomem(context);
    468     }
    469 
    470     return 0;
    471 }
    472 
    473 /**
    474  * Finish using the keytab in `id'.  All resources will be released,
    475  * even on errors.
    476  *
    477  * @param context a Keberos context.
    478  * @param id keytab to close.
    479  *
    480  * @return Return an error code or 0, see krb5_get_error_message().
    481  *
    482  * @ingroup krb5_keytab
    483  */
    484 
    485 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    486 krb5_kt_close(krb5_context context,
    487 	      krb5_keytab id)
    488 {
    489     krb5_error_code ret = 0;
    490 
    491     if (id) {
    492         ret = (id->close)(context, id);
    493         memset(id, 0, sizeof(*id));
    494         free(id);
    495     }
    496     return ret;
    497 }
    498 
    499 /**
    500  * Destroy (remove) the keytab in `id'.  All resources will be released,
    501  * even on errors, does the equvalment of krb5_kt_close() on the resources.
    502  *
    503  * @param context a Keberos context.
    504  * @param id keytab to destroy.
    505  *
    506  * @return Return an error code or 0, see krb5_get_error_message().
    507  *
    508  * @ingroup krb5_keytab
    509  */
    510 
    511 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    512 krb5_kt_destroy(krb5_context context,
    513 		krb5_keytab id)
    514 {
    515     krb5_error_code ret;
    516 
    517     ret = (*id->destroy)(context, id);
    518     krb5_kt_close(context, id);
    519     return ret;
    520 }
    521 
    522 /*
    523  * Match any aliases in keytab `entry' with `principal'.
    524  */
    525 
    526 static krb5_boolean
    527 compare_aliases(krb5_context context,
    528 		 krb5_keytab_entry *entry,
    529 		 krb5_const_principal principal)
    530 {
    531     unsigned int i;
    532     if (entry->aliases == NULL)
    533 	return FALSE;
    534     for (i = 0; i < entry->aliases->len; i++)
    535 	if (krb5_principal_compare(context, &entry->aliases->val[i], principal))
    536 	    return TRUE;
    537     return FALSE;
    538 }
    539 
    540 /**
    541  * Compare `entry' against `principal, vno, enctype'.
    542  * Any of `principal, vno, enctype' might be 0 which acts as a wildcard.
    543  * Return TRUE if they compare the same, FALSE otherwise.
    544  *
    545  * @param context a Keberos context.
    546  * @param entry an entry to match with.
    547  * @param principal principal to match, NULL matches all principals.
    548  * @param vno key version to match, 0 matches all key version numbers.
    549  * @param enctype encryption type to match, 0 matches all encryption types.
    550  *
    551  * @return Return TRUE or match, FALSE if not matched.
    552  *
    553  * @ingroup krb5_keytab
    554  */
    555 
    556 KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
    557 krb5_kt_compare(krb5_context context,
    558 		krb5_keytab_entry *entry,
    559 		krb5_const_principal principal,
    560 		krb5_kvno vno,
    561 		krb5_enctype enctype)
    562 {
    563     /* krb5_principal_compare() does not special-case the referral realm */
    564     if (principal != NULL && strcmp(principal->realm, "") == 0 &&
    565         !(krb5_principal_compare_any_realm(context, entry->principal, principal) ||
    566           compare_aliases(context, entry, principal))) {
    567         return FALSE;
    568     } else if (principal != NULL && strcmp(principal->realm, "") != 0 &&
    569         !(krb5_principal_compare(context, entry->principal, principal) ||
    570           compare_aliases(context, entry, principal))) {
    571 	return FALSE;
    572     }
    573     if (vno && vno != entry->vno)
    574 	return FALSE;
    575     if (enctype && enctype != entry->keyblock.keytype)
    576 	return FALSE;
    577     return TRUE;
    578 }
    579 
    580 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    581 _krb5_kt_principal_not_found(krb5_context context,
    582 			     krb5_error_code ret,
    583 			     krb5_keytab id,
    584 			     krb5_const_principal principal,
    585 			     krb5_enctype enctype,
    586 			     int kvno)
    587 {
    588     char princ[256], kvno_str[25], *kt_name;
    589     char *enctype_str = NULL;
    590 
    591     krb5_unparse_name_fixed (context, principal, princ, sizeof(princ));
    592     krb5_kt_get_full_name (context, id, &kt_name);
    593     if (enctype)
    594 	krb5_enctype_to_string(context, enctype, &enctype_str);
    595 
    596     if (kvno)
    597 	snprintf(kvno_str, sizeof(kvno_str), "(kvno %d)", kvno);
    598     else
    599 	kvno_str[0] = '\0';
    600 
    601     krb5_set_error_message (context, ret,
    602 			    N_("Failed to find %s%s in keytab %s (%s)",
    603 			       "principal, kvno, keytab file, enctype"),
    604 			    princ,
    605 			    kvno_str,
    606 			    kt_name ? kt_name : "unknown keytab",
    607 			    enctype_str ? enctype_str : "unknown enctype");
    608     free(kt_name);
    609     if (enctype_str)
    610 	free(enctype_str);
    611     return ret;
    612 }
    613 
    614 static krb5_error_code
    615 krb5_kt_get_entry_wrapped(krb5_context context,
    616 			  krb5_keytab id,
    617 			  krb5_const_principal principal,
    618 			  krb5_kvno kvno,
    619 			  krb5_enctype enctype,
    620 			  krb5_keytab_entry *entry)
    621 {
    622     krb5_keytab_entry tmp;
    623     krb5_error_code ret;
    624     krb5_kt_cursor cursor;
    625 
    626     if(id->get)
    627 	return (*id->get)(context, id, principal, kvno, enctype, entry);
    628 
    629     memset(&tmp, 0, sizeof(tmp));
    630     ret = krb5_kt_start_seq_get (context, id, &cursor);
    631     if (ret) {
    632 	/* This is needed for krb5_verify_init_creds, but keep error
    633 	 * string from previous error for the human. */
    634 	context->error_code = KRB5_KT_NOTFOUND;
    635 	return KRB5_KT_NOTFOUND;
    636     }
    637 
    638     entry->vno = 0;
    639     while (krb5_kt_next_entry(context, id, &tmp, &cursor) == 0) {
    640 	if (krb5_kt_compare(context, &tmp, principal, 0, enctype)) {
    641 	    /* the file keytab might only store the lower 8 bits of
    642 	       the kvno, so only compare those bits */
    643 	    if (kvno == tmp.vno
    644 		|| (tmp.vno < 256 && kvno % 256 == tmp.vno)) {
    645 		krb5_kt_copy_entry_contents (context, &tmp, entry);
    646 		krb5_kt_free_entry (context, &tmp);
    647 		krb5_kt_end_seq_get(context, id, &cursor);
    648 		return 0;
    649 	    } else if (kvno == 0 && tmp.vno > entry->vno) {
    650 		if (entry->vno)
    651 		    krb5_kt_free_entry (context, entry);
    652 		krb5_kt_copy_entry_contents (context, &tmp, entry);
    653 	    }
    654 	}
    655 	krb5_kt_free_entry(context, &tmp);
    656     }
    657     krb5_kt_end_seq_get (context, id, &cursor);
    658     if (entry->vno == 0)
    659 	return _krb5_kt_principal_not_found(context, KRB5_KT_NOTFOUND,
    660 					    id, principal, enctype, kvno);
    661     return 0;
    662 }
    663 
    664 /**
    665  * Retrieve the keytab entry for `principal, kvno, enctype' into `entry'
    666  * from the keytab `id'. Matching is done like krb5_kt_compare().
    667  *
    668  * @param context a Keberos context.
    669  * @param id a keytab.
    670  * @param principal principal to match, NULL matches all principals.
    671  * @param kvno key version to match, 0 matches all key version numbers.
    672  * @param enctype encryption type to match, 0 matches all encryption types.
    673  * @param entry the returned entry, free with krb5_kt_free_entry().
    674  *
    675  * @return Return an error code or 0, see krb5_get_error_message().
    676  *
    677  * @ingroup krb5_keytab
    678  */
    679 
    680 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    681 krb5_kt_get_entry(krb5_context context,
    682 		  krb5_keytab id,
    683 		  krb5_const_principal principal,
    684 		  krb5_kvno kvno,
    685 		  krb5_enctype enctype,
    686 		  krb5_keytab_entry *entry)
    687 {
    688     krb5_error_code ret;
    689     krb5_const_principal try_princ;
    690     krb5_name_canon_iterator name_canon_iter;
    691 
    692     if (!principal)
    693 	return krb5_kt_get_entry_wrapped(context, id, principal, kvno, enctype,
    694 					 entry);
    695 
    696     ret = krb5_name_canon_iterator_start(context, principal, &name_canon_iter);
    697     if (ret)
    698 	return ret;
    699 
    700     do {
    701 	ret = krb5_name_canon_iterate(context, &name_canon_iter, &try_princ,
    702                                       NULL);
    703 	if (ret)
    704 	    break;
    705         if (try_princ == NULL) {
    706             ret = KRB5_KT_NOTFOUND;
    707             continue;
    708         }
    709 	ret = krb5_kt_get_entry_wrapped(context, id, try_princ, kvno,
    710 					enctype, entry);
    711     } while (ret == KRB5_KT_NOTFOUND && name_canon_iter);
    712 
    713     if (ret != KRB5_KT_NOTFOUND)
    714 	krb5_set_error_message(context, ret,
    715 			       N_("Name canon failed while searching keytab",
    716 				  ""));
    717     krb5_free_name_canon_iterator(context, name_canon_iter);
    718     return ret;
    719 }
    720 
    721 /**
    722  * Copy the contents of `in' into `out'.
    723  *
    724  * @param context a Keberos context.
    725  * @param in the keytab entry to copy.
    726  * @param out the copy of the keytab entry, free with krb5_kt_free_entry().
    727  *
    728  * @return Return an error code or 0, see krb5_get_error_message().
    729  *
    730  * @ingroup krb5_keytab
    731  */
    732 
    733 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    734 krb5_kt_copy_entry_contents(krb5_context context,
    735 			    const krb5_keytab_entry *in,
    736 			    krb5_keytab_entry *out)
    737 {
    738     krb5_error_code ret;
    739 
    740     memset(out, 0, sizeof(*out));
    741 
    742     ret = krb5_copy_principal (context, in->principal, &out->principal);
    743     if (ret)
    744 	return ret;
    745     ret = krb5_copy_keyblock_contents (context,
    746 				       &in->keyblock,
    747 				       &out->keyblock);
    748     if (ret) {
    749         krb5_free_principal(context, out->principal);
    750         memset(out, 0, sizeof(*out));
    751         return ret;
    752     }
    753     out->vno = in->vno;
    754     out->timestamp = in->timestamp;
    755     return 0;
    756 }
    757 
    758 /**
    759  * Free the contents of `entry'.
    760  *
    761  * @param context a Keberos context.
    762  * @param entry the entry to free
    763  *
    764  * @return Return an error code or 0, see krb5_get_error_message().
    765  *
    766  * @ingroup krb5_keytab
    767  */
    768 
    769 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    770 krb5_kt_free_entry(krb5_context context,
    771 		   krb5_keytab_entry *entry)
    772 {
    773     krb5_free_principal (context, entry->principal);
    774     krb5_free_keyblock_contents (context, &entry->keyblock);
    775     memset(entry, 0, sizeof(*entry));
    776     return 0;
    777 }
    778 
    779 /**
    780  * Set `cursor' to point at the beginning of `id'.
    781  *
    782  * @param context a Keberos context.
    783  * @param id a keytab.
    784  * @param cursor a newly allocated cursor, free with krb5_kt_end_seq_get().
    785  *
    786  * @return Return an error code or 0, see krb5_get_error_message().
    787  *
    788  * @ingroup krb5_keytab
    789  */
    790 
    791 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    792 krb5_kt_start_seq_get(krb5_context context,
    793 		      krb5_keytab id,
    794 		      krb5_kt_cursor *cursor)
    795 {
    796     if(id->start_seq_get == NULL) {
    797 	krb5_set_error_message(context, HEIM_ERR_OPNOTSUPP,
    798 			       N_("start_seq_get is not supported "
    799 				  "in the %s keytab type", ""),
    800 			       id->prefix);
    801 	return HEIM_ERR_OPNOTSUPP;
    802     }
    803     return (*id->start_seq_get)(context, id, cursor);
    804 }
    805 
    806 /**
    807  * Get the next entry from keytab, advance the cursor.  On last entry
    808  * the function will return KRB5_KT_END.
    809  *
    810  * @param context a Keberos context.
    811  * @param id a keytab.
    812  * @param entry the returned entry, free with krb5_kt_free_entry().
    813  * @param cursor the cursor of the iteration.
    814  *
    815  * @return Return an error code or 0, see krb5_get_error_message().
    816  *
    817  * @ingroup krb5_keytab
    818  */
    819 
    820 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    821 krb5_kt_next_entry(krb5_context context,
    822 		   krb5_keytab id,
    823 		   krb5_keytab_entry *entry,
    824 		   krb5_kt_cursor *cursor)
    825 {
    826     if(id->next_entry == NULL) {
    827 	krb5_set_error_message(context, HEIM_ERR_OPNOTSUPP,
    828 			       N_("next_entry is not supported in the %s "
    829 				  " keytab", ""),
    830 			       id->prefix);
    831 	return HEIM_ERR_OPNOTSUPP;
    832     }
    833     return (*id->next_entry)(context, id, entry, cursor);
    834 }
    835 
    836 /**
    837  * Release all resources associated with `cursor'.
    838  *
    839  * @param context a Keberos context.
    840  * @param id a keytab.
    841  * @param cursor the cursor to free.
    842  *
    843  * @return Return an error code or 0, see krb5_get_error_message().
    844  *
    845  * @ingroup krb5_keytab
    846  */
    847 
    848 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    849 krb5_kt_end_seq_get(krb5_context context,
    850 		    krb5_keytab id,
    851 		    krb5_kt_cursor *cursor)
    852 {
    853     if(id->end_seq_get == NULL) {
    854 	krb5_set_error_message(context, HEIM_ERR_OPNOTSUPP,
    855 			       "end_seq_get is not supported in the %s "
    856 			       " keytab", id->prefix);
    857 	return HEIM_ERR_OPNOTSUPP;
    858     }
    859     return (*id->end_seq_get)(context, id, cursor);
    860 }
    861 
    862 /**
    863  * Add the entry in `entry' to the keytab `id'.
    864  *
    865  * @param context a Keberos context.
    866  * @param id a keytab.
    867  * @param entry the entry to add
    868  *
    869  * @return Return an error code or 0, see krb5_get_error_message().
    870  *
    871  * @ingroup krb5_keytab
    872  */
    873 
    874 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    875 krb5_kt_add_entry(krb5_context context,
    876 		  krb5_keytab id,
    877 		  krb5_keytab_entry *entry)
    878 {
    879     if(id->add == NULL) {
    880 	krb5_set_error_message(context, KRB5_KT_NOWRITE,
    881 			       N_("Add is not supported in the %s keytab", ""),
    882 			       id->prefix);
    883 	return KRB5_KT_NOWRITE;
    884     }
    885     entry->timestamp = time(NULL);
    886     return (*id->add)(context, id,entry);
    887 }
    888 
    889 /**
    890  * Remove an entry from the keytab, matching is done using
    891  * krb5_kt_compare().
    892 
    893  * @param context a Keberos context.
    894  * @param id a keytab.
    895  * @param entry the entry to remove
    896  *
    897  * @return Return an error code or 0, see krb5_get_error_message().
    898  *
    899  * @ingroup krb5_keytab
    900  */
    901 
    902 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    903 krb5_kt_remove_entry(krb5_context context,
    904 		     krb5_keytab id,
    905 		     krb5_keytab_entry *entry)
    906 {
    907     if(id->remove == NULL) {
    908 	krb5_set_error_message(context, KRB5_KT_NOWRITE,
    909 			       N_("Remove is not supported in the %s keytab", ""),
    910 			       id->prefix);
    911 	return KRB5_KT_NOWRITE;
    912     }
    913     return (*id->remove)(context, id, entry);
    914 }
    915 
    916 /**
    917  * Return true if the keytab exists and have entries
    918  *
    919  * @param context a Keberos context.
    920  * @param id a keytab.
    921  *
    922  * @return Return an error code or 0, see krb5_get_error_message().
    923  *
    924  * @ingroup krb5_keytab
    925  */
    926 
    927 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
    928 krb5_kt_have_content(krb5_context context,
    929 		     krb5_keytab id)
    930 {
    931     krb5_keytab_entry entry;
    932     krb5_kt_cursor cursor;
    933     krb5_error_code ret;
    934     char *name;
    935 
    936     memset(&entry, 0, sizeof(entry));
    937     ret = krb5_kt_start_seq_get(context, id, &cursor);
    938     if (ret)
    939 	goto notfound;
    940 
    941     ret = krb5_kt_next_entry(context, id, &entry, &cursor);
    942     krb5_kt_end_seq_get(context, id, &cursor);
    943     if (ret)
    944 	goto notfound;
    945 
    946     krb5_kt_free_entry(context, &entry);
    947 
    948     return 0;
    949 
    950  notfound:
    951     ret = krb5_kt_get_full_name(context, id, &name);
    952     if (ret == 0) {
    953 	krb5_set_error_message(context, KRB5_KT_NOTFOUND,
    954 			       N_("No entry in keytab: %s", ""), name);
    955 	free(name);
    956     }
    957     return KRB5_KT_NOTFOUND;
    958 }
    959