2N/A/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2N/A/*
2N/A * lib/krb5/keytab/kt_file.c
2N/A *
2N/A * Copyright 1990,1991,1995,2007,2008 by the Massachusetts Institute of Technology.
2N/A * All Rights Reserved.
2N/A *
2N/A * Export of this software from the United States of America may
2N/A * require a specific license from the United States Government.
2N/A * It is the responsibility of any person or organization contemplating
2N/A * export to obtain such a license before exporting.
2N/A *
2N/A * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
2N/A * distribute this software and its documentation for any purpose and
2N/A * without fee is hereby granted, provided that the above copyright
2N/A * notice appear in all copies and that both that copyright notice and
2N/A * this permission notice appear in supporting documentation, and that
2N/A * the name of M.I.T. not be used in advertising or publicity pertaining
2N/A * to distribution of the software without specific, written prior
2N/A * permission. Furthermore if you modify this software you must label
2N/A * your software as modified software and not distribute it in such a
2N/A * fashion that it might be confused with the original M.I.T. software.
2N/A * M.I.T. makes no representations about the suitability of
2N/A * this software for any purpose. It is provided "as is" without express
2N/A * or implied warranty.
2N/A *
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A#ifndef LEAN_CLIENT
2N/A
2N/A#include "k5-int.h"
2N/A#include <stdio.h>
2N/A/* Solaris Kerberos */
2N/A#include <locale.h>
2N/A
2N/A/*
2N/A * Information needed by internal routines of the file-based ticket
2N/A * cache implementation.
2N/A */
2N/A
2N/A
2N/A/*
2N/A * Constants
2N/A */
2N/A
2N/A#define KRB5_KT_VNO_1 0x0501 /* krb v5, keytab version 1 (DCE compat) */
2N/A#define KRB5_KT_VNO 0x0502 /* krb v5, keytab version 2 (standard) */
2N/A
2N/A#define KRB5_KT_DEFAULT_VNO KRB5_KT_VNO
2N/A
2N/A/*
2N/A * Types
2N/A */
2N/Atypedef struct _krb5_ktfile_data {
2N/A char *name; /* Name of the file */
2N/A FILE *openf; /* open file, if any. */
2N/A char iobuf[BUFSIZ]; /* so we can zap it later */
2N/A int version; /* Version number of keytab */
2N/A unsigned int iter_count; /* Number of active iterators */
2N/A long start_offset; /* Starting offset after version */
2N/A k5_mutex_t lock; /* Protect openf, version */
2N/A} krb5_ktfile_data;
2N/A
2N/A/*
2N/A * Some limitations:
2N/A *
2N/A * If the file OPENF is left open between calls, we have an iterator
2N/A * active, and OPENF is opened in read-only mode. So, no changes
2N/A * can be made via that handle.
2N/A *
2N/A * An advisory file lock is used while the file is open. Thus,
2N/A * multiple handles on the same underlying file cannot be used without
2N/A * disrupting the locking in effect.
2N/A *
2N/A * The start_offset field is only valid if the file is open. It will
2N/A * almost certainly always be the same constant. It's used so that
2N/A * if an iterator is active, and we start another one, we don't have
2N/A * to seek back to the start and re-read the version number to set
2N/A * the position for the iterator.
2N/A */
2N/A
2N/A/*
2N/A * Macros
2N/A */
2N/A#define KTPRIVATE(id) ((krb5_ktfile_data *)(id)->data)
2N/A#define KTFILENAME(id) (((krb5_ktfile_data *)(id)->data)->name)
2N/A#define KTFILEP(id) (((krb5_ktfile_data *)(id)->data)->openf)
2N/A#define KTFILEBUFP(id) (((krb5_ktfile_data *)(id)->data)->iobuf)
2N/A#define KTVERSION(id) (((krb5_ktfile_data *)(id)->data)->version)
2N/A#define KTITERS(id) (((krb5_ktfile_data *)(id)->data)->iter_count)
2N/A#define KTSTARTOFF(id) (((krb5_ktfile_data *)(id)->data)->start_offset)
2N/A#define KTLOCK(id) k5_mutex_lock(&((krb5_ktfile_data *)(id)->data)->lock)
2N/A#define KTUNLOCK(id) k5_mutex_unlock(&((krb5_ktfile_data *)(id)->data)->lock)
2N/A#define KTCHECKLOCK(id) k5_mutex_assert_locked(&((krb5_ktfile_data *)(id)->data)->lock)
2N/A
2N/Aextern const struct _krb5_kt_ops krb5_ktf_ops;
2N/Aextern const struct _krb5_kt_ops krb5_ktf_writable_ops;
2N/A
2N/A/* Solaris Kerberos */
2N/Aextern krb5_boolean KRB5_CALLCONV
2N/A__krb5_principal_compare_case_ins(krb5_context context,
2N/A krb5_const_principal princ1,
2N/A krb5_const_principal princ2);
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_resolve(krb5_context, const char *, krb5_keytab *);
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_wresolve(krb5_context, const char *, krb5_keytab *);
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_get_name(krb5_context, krb5_keytab, char *, unsigned int);
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_close(krb5_context, krb5_keytab);
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_get_entry(krb5_context, krb5_keytab, krb5_const_principal,
2N/A krb5_kvno, krb5_enctype, krb5_keytab_entry *);
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_start_seq_get(krb5_context, krb5_keytab, krb5_kt_cursor *);
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_get_next(krb5_context, krb5_keytab, krb5_keytab_entry *,
2N/A krb5_kt_cursor *);
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_end_get(krb5_context, krb5_keytab, krb5_kt_cursor *);
2N/A
2N/A/* routines to be included on extended version (write routines) */
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_add(krb5_context, krb5_keytab, krb5_keytab_entry *);
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_remove(krb5_context, krb5_keytab, krb5_keytab_entry *);
2N/A
2N/A/* Solaris Kerberos */
2N/Akrb5_error_code
2N/Akrb5_ktfileint_openr(krb5_context, krb5_keytab);
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_openw(krb5_context, krb5_keytab);
2N/A
2N/A/* Solaris Kerberos */
2N/Akrb5_error_code
2N/Akrb5_ktfileint_close(krb5_context, krb5_keytab);
2N/A
2N/A/* Solaris Kerberos */
2N/Akrb5_error_code
2N/Akrb5_ktfileint_read_entry(krb5_context, krb5_keytab, krb5_keytab_entry *);
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_write_entry(krb5_context, krb5_keytab, krb5_keytab_entry *);
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_delete_entry(krb5_context, krb5_keytab, krb5_int32);
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_internal_read_entry(krb5_context, krb5_keytab,
2N/A krb5_keytab_entry *, krb5_int32 *);
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_size_entry(krb5_context, krb5_keytab_entry *, krb5_int32 *);
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_find_slot(krb5_context, krb5_keytab, krb5_int32 *,
2N/A krb5_int32 *);
2N/A
2N/A
2N/A/*
2N/A * This is an implementation specific resolver. It returns a keytab id
2N/A * initialized with file keytab routines.
2N/A */
2N/A
2N/Astatic krb5_error_code
2N/Aktfile_common_resolve(krb5_context context, const char *name,
2N/A krb5_keytab *idptr, const struct _krb5_kt_ops *ops)
2N/A{
2N/A krb5_ktfile_data *data = NULL;
2N/A krb5_error_code err = ENOMEM;
2N/A krb5_keytab id;
2N/A
2N/A *idptr = NULL;
2N/A
2N/A id = calloc(1, sizeof(*id));
2N/A if (id == NULL)
2N/A return ENOMEM;
2N/A
2N/A id->ops = ops;
2N/A data = calloc(1, sizeof(krb5_ktfile_data));
2N/A if (data == NULL)
2N/A goto cleanup;
2N/A
2N/A data->name = strdup(name);
2N/A if (data->name == NULL)
2N/A goto cleanup;
2N/A
2N/A err = k5_mutex_init(&data->lock);
2N/A if (err)
2N/A goto cleanup;
2N/A
2N/A data->openf = 0;
2N/A data->version = 0;
2N/A data->iter_count = 0;
2N/A
2N/A id->data = (krb5_pointer) data;
2N/A id->magic = KV5M_KEYTAB;
2N/A *idptr = id;
2N/A return 0;
2N/Acleanup:
2N/A if (data)
2N/A free(data->name);
2N/A free(data);
2N/A free(id);
2N/A return err;
2N/A}
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_resolve(krb5_context context, const char *name, krb5_keytab *id)
2N/A{
2N/A return ktfile_common_resolve(context, name, id, &krb5_ktf_ops);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * "Close" a file-based keytab and invalidate the id. This means
2N/A * free memory hidden in the structures.
2N/A */
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_close(krb5_context context, krb5_keytab id)
2N/A/*
2N/A * This routine is responsible for freeing all memory allocated
2N/A * for this keytab. There are no system resources that need
2N/A * to be freed nor are there any open files.
2N/A *
2N/A * This routine should undo anything done by krb5_ktfile_resolve().
2N/A */
2N/A{
2N/A free(KTFILENAME(id));
2N/A zap(KTFILEBUFP(id), BUFSIZ);
2N/A k5_mutex_destroy(&((krb5_ktfile_data *)id->data)->lock);
2N/A free(id->data);
2N/A id->ops = 0;
2N/A free(id);
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * This is the get_entry routine for the file based keytab implementation.
2N/A * It opens the keytab file, and either retrieves the entry or returns
2N/A * an error.
2N/A */
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_get_entry(krb5_context context, krb5_keytab id,
2N/A krb5_const_principal principal, krb5_kvno kvno,
2N/A krb5_enctype enctype, krb5_keytab_entry *entry)
2N/A{
2N/A krb5_keytab_entry cur_entry, new_entry;
2N/A krb5_error_code kerror = 0;
2N/A int found_wrong_kvno = 0;
2N/A krb5_boolean similar;
2N/A int kvno_offset = 0;
2N/A int was_open;
2N/A
2N/A kerror = KTLOCK(id);
2N/A if (kerror)
2N/A return kerror;
2N/A
2N/A if (KTFILEP(id) != NULL) {
2N/A was_open = 1;
2N/A
2N/A if (fseek(KTFILEP(id), KTSTARTOFF(id), SEEK_SET) == -1) {
2N/A KTUNLOCK(id);
2N/A return errno;
2N/A }
2N/A } else {
2N/A was_open = 0;
2N/A
2N/A /* Open the keyfile for reading */
2N/A if ((kerror = krb5_ktfileint_openr(context, id))) {
2N/A KTUNLOCK(id);
2N/A return(kerror);
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * For efficiency and simplicity, we'll use a while true that
2N/A * is exited with a break statement.
2N/A */
2N/A cur_entry.principal = 0;
2N/A cur_entry.vno = 0;
2N/A cur_entry.key.contents = 0;
2N/A
2N/A while (TRUE) {
2N/A if ((kerror = krb5_ktfileint_read_entry(context, id, &new_entry)))
2N/A break;
2N/A
2N/A /* by the time this loop exits, it must either free cur_entry,
2N/A and copy new_entry there, or free new_entry. Otherwise, it
2N/A leaks. */
2N/A
2N/A /* if the principal isn't the one requested, free new_entry
2N/A and continue to the next. */
2N/A
2N/A /*
2N/A * Solaris Kerberos: MS Interop requires that case insensitive
2N/A * comparisons of service and host components are performed for key
2N/A * table lookup, etc. Only called if the private environment variable
2N/A * MS_INTEROP is defined.
2N/A */
2N/A if (krb5_getenv("MS_INTEROP")) {
2N/A if (!krb5_principal_compare_flags(context, principal,
2N/A new_entry.principal,
2N/A KRB5_PRINCIPAL_COMPARE_CASEFOLD)) {
2N/A krb5_kt_free_entry(context, &new_entry);
2N/A continue;
2N/A }
2N/A } else if (!krb5_principal_compare(context, principal,
2N/A new_entry.principal)) {
2N/A krb5_kt_free_entry(context, &new_entry);
2N/A continue;
2N/A }
2N/A
2N/A /* if the enctype is not ignored and doesn't match, free new_entry
2N/A and continue to the next */
2N/A
2N/A if (enctype != IGNORE_ENCTYPE) {
2N/A if ((kerror = krb5_c_enctype_compare(context, enctype,
2N/A new_entry.key.enctype,
2N/A &similar))) {
2N/A krb5_kt_free_entry(context, &new_entry);
2N/A break;
2N/A }
2N/A
2N/A if (!similar) {
2N/A krb5_kt_free_entry(context, &new_entry);
2N/A continue;
2N/A }
2N/A /*
2N/A * Coerce the enctype of the output keyblock in case we
2N/A * got an inexact match on the enctype.
2N/A */
2N/A new_entry.key.enctype = enctype;
2N/A
2N/A }
2N/A
2N/A if (kvno == IGNORE_VNO) {
2N/A /* if this is the first match, or if the new vno is
2N/A bigger, free the current and keep the new. Otherwise,
2N/A free the new. */
2N/A /* A 1.2.x keytab contains only the low 8 bits of the key
2N/A version number. Since it can be much bigger, and thus
2N/A the 8-bit value can wrap, we need some heuristics to
2N/A figure out the "highest" numbered key if some numbers
2N/A close to 255 and some near 0 are used.
2N/A
2N/A The heuristic here:
2N/A
2N/A If we have any keys with versions over 240, then assume
2N/A that all version numbers 0-127 refer to 256+N instead.
2N/A Not perfect, but maybe good enough? */
2N/A
2N/A#define M(VNO) (((VNO) - kvno_offset + 256) % 256)
2N/A
2N/A if (new_entry.vno > 240)
2N/A kvno_offset = 128;
2N/A if (! cur_entry.principal ||
2N/A M(new_entry.vno) > M(cur_entry.vno)) {
2N/A krb5_kt_free_entry(context, &cur_entry);
2N/A cur_entry = new_entry;
2N/A } else {
2N/A krb5_kt_free_entry(context, &new_entry);
2N/A }
2N/A } else {
2N/A /* if this kvno matches, free the current (will there ever
2N/A be one?), keep the new, and break out. Otherwise, remember
2N/A that we were here so we can return the right error, and
2N/A free the new */
2N/A /* Yuck. The krb5-1.2.x keytab format only stores one byte
2N/A for the kvno, so we're toast if the kvno requested is
2N/A higher than that. Short-term workaround: only compare
2N/A the low 8 bits. */
2N/A
2N/A if (new_entry.vno == (kvno & 0xff)) {
2N/A krb5_kt_free_entry(context, &cur_entry);
2N/A cur_entry = new_entry;
2N/A break;
2N/A } else {
2N/A found_wrong_kvno++;
2N/A krb5_kt_free_entry(context, &new_entry);
2N/A }
2N/A }
2N/A }
2N/A
2N/A if (kerror == KRB5_KT_END) {
2N/A if (cur_entry.principal)
2N/A kerror = 0;
2N/A else if (found_wrong_kvno)
2N/A kerror = KRB5_KT_KVNONOTFOUND;
2N/A else
2N/A kerror = KRB5_KT_NOTFOUND;
2N/A }
2N/A if (kerror) {
2N/A if (was_open == 0)
2N/A (void) krb5_ktfileint_close(context, id);
2N/A KTUNLOCK(id);
2N/A krb5_kt_free_entry(context, &cur_entry);
2N/A return kerror;
2N/A }
2N/A if (was_open == 0 && (kerror = krb5_ktfileint_close(context, id)) != 0) {
2N/A KTUNLOCK(id);
2N/A krb5_kt_free_entry(context, &cur_entry);
2N/A return kerror;
2N/A }
2N/A KTUNLOCK(id);
2N/A *entry = cur_entry;
2N/A return 0;
2N/A}
2N/A
2N/A/*
2N/A * Get the name of the file containing a file-based keytab.
2N/A */
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_get_name(krb5_context context, krb5_keytab id, char *name, unsigned int len)
2N/A/*
2N/A * This routine returns the name of the name of the file associated with
2N/A * this file-based keytab. name is zeroed and the filename is truncated
2N/A * to fit in name if necessary. The name is prefixed with PREFIX:, so that
2N/A * trt will happen if the name is passed back to resolve.
2N/A */
2N/A{
2N/A int result;
2N/A
2N/A memset(name, 0, len);
2N/A result = snprintf(name, len, "%s:%s", id->ops->prefix, KTFILENAME(id));
2N/A if (SNPRINTF_OVERFLOW(result, len))
2N/A return(KRB5_KT_NAME_TOOLONG);
2N/A return(0);
2N/A}
2N/A
2N/A/*
2N/A * krb5_ktfile_start_seq_get()
2N/A */
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_start_seq_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursorp)
2N/A{
2N/A krb5_error_code retval;
2N/A long *fileoff;
2N/A
2N/A retval = KTLOCK(id);
2N/A if (retval)
2N/A return retval;
2N/A
2N/A if (KTITERS(id) == 0) {
2N/A if ((retval = krb5_ktfileint_openr(context, id))) {
2N/A KTUNLOCK(id);
2N/A return retval;
2N/A }
2N/A }
2N/A
2N/A if (!(fileoff = (long *)malloc(sizeof(*fileoff)))) {
2N/A if (KTITERS(id) == 0)
2N/A krb5_ktfileint_close(context, id);
2N/A KTUNLOCK(id);
2N/A return ENOMEM;
2N/A }
2N/A *fileoff = KTSTARTOFF(id);
2N/A *cursorp = (krb5_kt_cursor)fileoff;
2N/A KTITERS(id)++;
2N/A if (KTITERS(id) == 0) {
2N/A /* Wrapped?! */
2N/A KTITERS(id)--;
2N/A KTUNLOCK(id);
2N/A krb5_set_error_message(context, KRB5_KT_IOERR,
2N/A "Too many keytab iterators active");
2N/A return KRB5_KT_IOERR; /* XXX */
2N/A }
2N/A KTUNLOCK(id);
2N/A
2N/A return 0;
2N/A}
2N/A
2N/A/*
2N/A * krb5_ktfile_get_next()
2N/A */
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_get_next(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry, krb5_kt_cursor *cursor)
2N/A{
2N/A long *fileoff = (long *)*cursor;
2N/A krb5_keytab_entry cur_entry;
2N/A krb5_error_code kerror;
2N/A
2N/A kerror = KTLOCK(id);
2N/A if (kerror)
2N/A return kerror;
2N/A if (KTFILEP(id) == NULL) {
2N/A KTUNLOCK(id);
2N/A return KRB5_KT_IOERR;
2N/A }
2N/A if (fseek(KTFILEP(id), *fileoff, 0) == -1) {
2N/A KTUNLOCK(id);
2N/A return KRB5_KT_END;
2N/A }
2N/A if ((kerror = krb5_ktfileint_read_entry(context, id, &cur_entry))) {
2N/A KTUNLOCK(id);
2N/A return kerror;
2N/A }
2N/A *fileoff = ftell(KTFILEP(id));
2N/A *entry = cur_entry;
2N/A KTUNLOCK(id);
2N/A return 0;
2N/A}
2N/A
2N/A/*
2N/A * krb5_ktfile_end_get()
2N/A */
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_end_get(krb5_context context, krb5_keytab id, krb5_kt_cursor *cursor)
2N/A{
2N/A krb5_error_code kerror;
2N/A
2N/A free(*cursor);
2N/A kerror = KTLOCK(id);
2N/A if (kerror)
2N/A return kerror;
2N/A KTITERS(id)--;
2N/A if (KTFILEP(id) != NULL && KTITERS(id) == 0)
2N/A kerror = krb5_ktfileint_close(context, id);
2N/A else
2N/A kerror = 0;
2N/A KTUNLOCK(id);
2N/A return kerror;
2N/A}
2N/A
2N/A/*
2N/A * ser_ktf.c - Serialize keytab file context for subsequent reopen.
2N/A */
2N/A
2N/Astatic const char ktfile_def_name[] = ".";
2N/A
2N/A/*
2N/A * Routines to deal with externalizing krb5_keytab for [WR]FILE: variants.
2N/A * krb5_ktf_keytab_size();
2N/A * krb5_ktf_keytab_externalize();
2N/A * krb5_ktf_keytab_internalize();
2N/A */
2N/Astatic krb5_error_code
2N/Akrb5_ktf_keytab_size(krb5_context, krb5_pointer, size_t *);
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktf_keytab_externalize(krb5_context, krb5_pointer, krb5_octet **,
2N/A size_t *);
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktf_keytab_internalize(krb5_context,krb5_pointer *, krb5_octet **,
2N/A size_t *);
2N/A
2N/A/*
2N/A * Serialization entry for this type.
2N/A */
2N/Aconst krb5_ser_entry krb5_ktfile_ser_entry = {
2N/A KV5M_KEYTAB, /* Type */
2N/A krb5_ktf_keytab_size, /* Sizer routine */
2N/A krb5_ktf_keytab_externalize, /* Externalize routine */
2N/A krb5_ktf_keytab_internalize /* Internalize routine */
2N/A};
2N/A
2N/A/*
2N/A * krb5_ktf_keytab_size() - Determine the size required to externalize
2N/A * this krb5_keytab variant.
2N/A */
2N/Astatic krb5_error_code
2N/Akrb5_ktf_keytab_size(krb5_context kcontext, krb5_pointer arg, size_t *sizep)
2N/A{
2N/A krb5_error_code kret;
2N/A krb5_keytab keytab;
2N/A size_t required;
2N/A krb5_ktfile_data *ktdata;
2N/A
2N/A kret = EINVAL;
2N/A if ((keytab = (krb5_keytab) arg)) {
2N/A /*
2N/A * Saving FILE: variants of krb5_keytab requires at minimum:
2N/A * krb5_int32 for KV5M_KEYTAB
2N/A * krb5_int32 for length of keytab name.
2N/A * krb5_int32 for file status.
2N/A * krb5_int32 for file position.
2N/A * krb5_int32 for file position.
2N/A * krb5_int32 for version.
2N/A * krb5_int32 for KV5M_KEYTAB
2N/A */
2N/A required = sizeof(krb5_int32) * 7;
2N/A if (keytab->ops && keytab->ops->prefix)
2N/A required += (strlen(keytab->ops->prefix)+1);
2N/A
2N/A /*
2N/A * The keytab name is formed as follows:
2N/A * <prefix>:<name>
2N/A * If there's no name, we use a default name so that we have something
2N/A * to call krb5_keytab_resolve with.
2N/A */
2N/A ktdata = (krb5_ktfile_data *) keytab->data;
2N/A required += strlen((ktdata && ktdata->name) ?
2N/A ktdata->name : ktfile_def_name);
2N/A kret = 0;
2N/A
2N/A if (!kret)
2N/A *sizep += required;
2N/A }
2N/A return(kret);
2N/A}
2N/A
2N/A/*
2N/A * krb5_ktf_keytab_externalize() - Externalize the krb5_keytab.
2N/A */
2N/Astatic krb5_error_code
2N/Akrb5_ktf_keytab_externalize(krb5_context kcontext, krb5_pointer arg, krb5_octet **buffer, size_t *lenremain)
2N/A{
2N/A krb5_error_code kret;
2N/A krb5_keytab keytab;
2N/A size_t required;
2N/A krb5_octet *bp;
2N/A size_t remain;
2N/A krb5_ktfile_data *ktdata;
2N/A krb5_int32 file_is_open;
2N/A krb5_int64 file_pos;
2N/A char *ktname;
2N/A const char *fnamep;
2N/A
2N/A required = 0;
2N/A bp = *buffer;
2N/A remain = *lenremain;
2N/A kret = EINVAL;
2N/A if ((keytab = (krb5_keytab) arg)) {
2N/A kret = ENOMEM;
2N/A if (!krb5_ktf_keytab_size(kcontext, arg, &required) &&
2N/A (required <= remain)) {
2N/A /* Our identifier */
2N/A (void) krb5_ser_pack_int32(KV5M_KEYTAB, &bp, &remain);
2N/A
2N/A ktdata = (krb5_ktfile_data *) keytab->data;
2N/A file_is_open = 0;
2N/A file_pos = 0;
2N/A
2N/A /* Calculate the length of the name */
2N/A if (ktdata && ktdata->name)
2N/A fnamep = ktdata->name;
2N/A else
2N/A fnamep = ktfile_def_name;
2N/A
2N/A if (keytab->ops && keytab->ops->prefix) {
2N/A if (asprintf(&ktname, "%s:%s", keytab->ops->prefix, fnamep) < 0)
2N/A ktname = NULL;
2N/A } else
2N/A ktname = strdup(fnamep);
2N/A
2N/A if (ktname) {
2N/A /* Fill in the file-specific keytab information. */
2N/A if (ktdata) {
2N/A if (ktdata->openf) {
2N/A long fpos;
2N/A int fflags = 0;
2N/A
2N/A file_is_open = 1;
2N/A#if !defined(_WIN32)
2N/A fflags = fcntl(fileno(ktdata->openf), F_GETFL, 0);
2N/A if (fflags > 0)
2N/A file_is_open |= ((fflags & O_ACCMODE) << 1);
2N/A#else
2N/A file_is_open = 0;
2N/A#endif
2N/A fpos = ftell(ktdata->openf);
2N/A file_pos = fpos; /* XX range check? */
2N/A }
2N/A }
2N/A
2N/A /* Put the length of the file name */
2N/A (void) krb5_ser_pack_int32((krb5_int32) strlen(ktname),
2N/A &bp, &remain);
2N/A
2N/A /* Put the name */
2N/A (void) krb5_ser_pack_bytes((krb5_octet *) ktname,
2N/A strlen(ktname),
2N/A &bp, &remain);
2N/A
2N/A /* Put the file open flag */
2N/A (void) krb5_ser_pack_int32(file_is_open, &bp, &remain);
2N/A
2N/A /* Put the file position */
2N/A (void) krb5_ser_pack_int64(file_pos, &bp, &remain);
2N/A
2N/A /* Put the version */
2N/A (void) krb5_ser_pack_int32((krb5_int32) ((ktdata) ?
2N/A ktdata->version : 0),
2N/A &bp, &remain);
2N/A
2N/A /* Put the trailer */
2N/A (void) krb5_ser_pack_int32(KV5M_KEYTAB, &bp, &remain);
2N/A kret = 0;
2N/A *buffer = bp;
2N/A *lenremain = remain;
2N/A free(ktname);
2N/A }
2N/A }
2N/A }
2N/A return(kret);
2N/A}
2N/A
2N/A/*
2N/A * krb5_ktf_keytab_internalize() - Internalize the krb5_ktf_keytab.
2N/A */
2N/Astatic krb5_error_code
2N/Akrb5_ktf_keytab_internalize(krb5_context kcontext, krb5_pointer *argp, krb5_octet **buffer, size_t *lenremain)
2N/A{
2N/A krb5_error_code kret;
2N/A krb5_keytab keytab = NULL;
2N/A krb5_int32 ibuf;
2N/A krb5_octet *bp;
2N/A size_t remain;
2N/A char *ktname = NULL;
2N/A krb5_ktfile_data *ktdata;
2N/A krb5_int32 file_is_open;
2N/A krb5_int64 foff;
2N/A
2N/A *argp = NULL;
2N/A bp = *buffer;
2N/A remain = *lenremain;
2N/A
2N/A /* Read our magic number */
2N/A if (krb5_ser_unpack_int32(&ibuf, &bp, &remain) || ibuf != KV5M_KEYTAB)
2N/A return EINVAL;
2N/A
2N/A /* Read the keytab name */
2N/A kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
2N/A if (kret)
2N/A return kret;
2N/A ktname = malloc(ibuf + 1);
2N/A if (!ktname)
2N/A return ENOMEM;
2N/A kret = krb5_ser_unpack_bytes((krb5_octet *) ktname, (size_t) ibuf,
2N/A &bp, &remain);
2N/A if (kret)
2N/A goto cleanup;
2N/A ktname[ibuf] = '\0';
2N/A
2N/A /* Resolve the keytab. */
2N/A kret = krb5_kt_resolve(kcontext, ktname, &keytab);
2N/A if (kret)
2N/A goto cleanup;
2N/A
2N/A if (keytab->ops != &krb5_ktf_writable_ops
2N/A && keytab->ops != &krb5_ktf_ops) {
2N/A kret = EINVAL;
2N/A goto cleanup;
2N/A }
2N/A ktdata = (krb5_ktfile_data *) keytab->data;
2N/A
2N/A if (remain < (sizeof(krb5_int32)*5)) {
2N/A kret = EINVAL;
2N/A goto cleanup;
2N/A }
2N/A (void) krb5_ser_unpack_int32(&file_is_open, &bp, &remain);
2N/A (void) krb5_ser_unpack_int64(&foff, &bp, &remain);
2N/A (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
2N/A ktdata->version = (int) ibuf;
2N/A (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
2N/A if (ibuf != KV5M_KEYTAB) {
2N/A kret = EINVAL;
2N/A goto cleanup;
2N/A }
2N/A
2N/A if (file_is_open) {
2N/A int fmode;
2N/A long fpos;
2N/A
2N/A#if !defined(_WIN32)
2N/A fmode = (file_is_open >> 1) & O_ACCMODE;
2N/A#else
2N/A fmode = 0;
2N/A#endif
2N/A if (fmode)
2N/A kret = krb5_ktfileint_openw(kcontext, keytab);
2N/A else
2N/A kret = krb5_ktfileint_openr(kcontext, keytab);
2N/A if (kret)
2N/A goto cleanup;
2N/A fpos = foff; /* XX range check? */
2N/A if (fseek(KTFILEP(keytab), fpos, SEEK_SET) == -1) {
2N/A kret = errno;
2N/A goto cleanup;
2N/A }
2N/A }
2N/A
2N/A *buffer = bp;
2N/A *lenremain = remain;
2N/A *argp = (krb5_pointer) keytab;
2N/Acleanup:
2N/A if (kret != 0 && keytab)
2N/A krb5_kt_close(kcontext, keytab);
2N/A free(ktname);
2N/A return kret;
2N/A}
2N/A
2N/A/*
2N/A * This is an implementation specific resolver. It returns a keytab id
2N/A * initialized with file keytab routines.
2N/A */
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_wresolve(krb5_context context, const char *name, krb5_keytab *id)
2N/A{
2N/A return ktfile_common_resolve(context, name, id, &krb5_ktf_writable_ops);
2N/A}
2N/A
2N/A
2N/A/*
2N/A * krb5_ktfile_add()
2N/A */
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_add(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
2N/A{
2N/A krb5_error_code retval;
2N/A
2N/A retval = KTLOCK(id);
2N/A if (retval)
2N/A return retval;
2N/A if (KTFILEP(id)) {
2N/A /* Iterator(s) active -- no changes. */
2N/A KTUNLOCK(id);
2N/A krb5_set_error_message(context, KRB5_KT_IOERR,
2N/A "Cannot change keytab with keytab iterators active");
2N/A return KRB5_KT_IOERR; /* XXX */
2N/A }
2N/A if ((retval = krb5_ktfileint_openw(context, id))) {
2N/A KTUNLOCK(id);
2N/A return retval;
2N/A }
2N/A if (fseek(KTFILEP(id), 0, 2) == -1) {
2N/A KTUNLOCK(id);
2N/A return KRB5_KT_END;
2N/A }
2N/A retval = krb5_ktfileint_write_entry(context, id, entry);
2N/A krb5_ktfileint_close(context, id);
2N/A KTUNLOCK(id);
2N/A return retval;
2N/A}
2N/A
2N/A/*
2N/A * krb5_ktfile_remove()
2N/A */
2N/A
2N/Astatic krb5_error_code KRB5_CALLCONV
2N/Akrb5_ktfile_remove(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
2N/A{
2N/A krb5_keytab_entry cur_entry;
2N/A krb5_error_code kerror;
2N/A krb5_int32 delete_point;
2N/A
2N/A kerror = KTLOCK(id);
2N/A if (kerror)
2N/A return kerror;
2N/A if (KTFILEP(id)) {
2N/A /* Iterator(s) active -- no changes. */
2N/A KTUNLOCK(id);
2N/A krb5_set_error_message(context, KRB5_KT_IOERR,
2N/A "Cannot change keytab with keytab iterators active");
2N/A return KRB5_KT_IOERR; /* XXX */
2N/A }
2N/A
2N/A if ((kerror = krb5_ktfileint_openw(context, id))) {
2N/A KTUNLOCK(id);
2N/A return kerror;
2N/A }
2N/A
2N/A /*
2N/A * For efficiency and simplicity, we'll use a while true that
2N/A * is exited with a break statement.
2N/A */
2N/A while (TRUE) {
2N/A if ((kerror = krb5_ktfileint_internal_read_entry(context, id,
2N/A &cur_entry,
2N/A &delete_point)))
2N/A break;
2N/A
2N/A if ((entry->vno == cur_entry.vno) &&
2N/A (entry->key.enctype == cur_entry.key.enctype) &&
2N/A krb5_principal_compare(context, entry->principal, cur_entry.principal)) {
2N/A /* found a match */
2N/A krb5_kt_free_entry(context, &cur_entry);
2N/A break;
2N/A }
2N/A krb5_kt_free_entry(context, &cur_entry);
2N/A }
2N/A
2N/A if (kerror == KRB5_KT_END)
2N/A kerror = KRB5_KT_NOTFOUND;
2N/A
2N/A if (kerror) {
2N/A (void) krb5_ktfileint_close(context, id);
2N/A KTUNLOCK(id);
2N/A return kerror;
2N/A }
2N/A
2N/A kerror = krb5_ktfileint_delete_entry(context, id, delete_point);
2N/A
2N/A if (kerror) {
2N/A (void) krb5_ktfileint_close(context, id);
2N/A } else {
2N/A kerror = krb5_ktfileint_close(context, id);
2N/A }
2N/A KTUNLOCK(id);
2N/A return kerror;
2N/A}
2N/A
2N/A/*
2N/A * krb5_ktf_ops
2N/A */
2N/A
2N/Aconst struct _krb5_kt_ops krb5_ktf_ops = {
2N/A 0,
2N/A "FILE", /* Prefix -- this string should not appear anywhere else! */
2N/A krb5_ktfile_resolve,
2N/A krb5_ktfile_get_name,
2N/A krb5_ktfile_close,
2N/A krb5_ktfile_get_entry,
2N/A krb5_ktfile_start_seq_get,
2N/A krb5_ktfile_get_next,
2N/A krb5_ktfile_end_get,
2N/A 0,
2N/A 0,
2N/A &krb5_ktfile_ser_entry
2N/A};
2N/A
2N/A/*
2N/A * krb5_ktf_writable_ops
2N/A */
2N/A
2N/Aconst struct _krb5_kt_ops krb5_ktf_writable_ops = {
2N/A 0,
2N/A "WRFILE", /* Prefix -- this string should not appear anywhere else! */
2N/A krb5_ktfile_wresolve,
2N/A krb5_ktfile_get_name,
2N/A krb5_ktfile_close,
2N/A krb5_ktfile_get_entry,
2N/A krb5_ktfile_start_seq_get,
2N/A krb5_ktfile_get_next,
2N/A krb5_ktfile_end_get,
2N/A krb5_ktfile_add,
2N/A krb5_ktfile_remove,
2N/A &krb5_ktfile_ser_entry
2N/A};
2N/A
2N/A/*
2N/A * krb5_kt_dfl_ops
2N/A */
2N/A
2N/Aconst krb5_kt_ops krb5_kt_dfl_ops = {
2N/A 0,
2N/A "FILE", /* Prefix -- this string should not appear anywhere else! */
2N/A krb5_ktfile_resolve,
2N/A krb5_ktfile_get_name,
2N/A krb5_ktfile_close,
2N/A krb5_ktfile_get_entry,
2N/A krb5_ktfile_start_seq_get,
2N/A krb5_ktfile_get_next,
2N/A krb5_ktfile_end_get,
2N/A 0,
2N/A 0,
2N/A &krb5_ktfile_ser_entry
2N/A};
2N/A
2N/A/*
2N/A * lib/krb5/keytab/file/ktf_util.c
2N/A *
2N/A * Copyright (c) Hewlett-Packard Company 1991
2N/A * Released to the Massachusetts Institute of Technology for inclusion
2N/A * in the Kerberos source code distribution.
2N/A *
2N/A * Copyright 1990,1991 by the Massachusetts Institute of Technology.
2N/A * All Rights Reserved.
2N/A *
2N/A * Export of this software from the United States of America may
2N/A * require a specific license from the United States Government.
2N/A * It is the responsibility of any person or organization contemplating
2N/A * export to obtain such a license before exporting.
2N/A *
2N/A * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
2N/A * distribute this software and its documentation for any purpose and
2N/A * without fee is hereby granted, provided that the above copyright
2N/A * notice appear in all copies and that both that copyright notice and
2N/A * this permission notice appear in supporting documentation, and that
2N/A * the name of M.I.T. not be used in advertising or publicity pertaining
2N/A * to distribution of the software without specific, written prior
2N/A * permission. Furthermore if you modify this software you must label
2N/A * your software as modified software and not distribute it in such a
2N/A * fashion that it might be confused with the original M.I.T. software.
2N/A * M.I.T. makes no representations about the suitability of
2N/A * this software for any purpose. It is provided "as is" without express
2N/A * or implied warranty.
2N/A *
2N/A *
2N/A * This function contains utilities for the file based implementation of
2N/A * the keytab. There are no public functions in this file.
2N/A *
2N/A * This file is the only one that has knowledge of the format of a
2N/A * keytab file.
2N/A *
2N/A * The format is as follows:
2N/A *
2N/A * <file format vno>
2N/A * <record length>
2N/A * principal timestamp vno key
2N/A * <record length>
2N/A * principal timestamp vno key
2N/A * ....
2N/A *
2N/A * A length field (sizeof(krb5_int32)) exists between entries. When this
2N/A * length is positive it indicates an active entry, when negative a hole.
2N/A * The length indicates the size of the block in the file (this may be
2N/A * larger than the size of the next record, since we are using a first
2N/A * fit algorithm for re-using holes and the first fit may be larger than
2N/A * the entry we are writing). Another (compatible) implementation could
2N/A * break up holes when allocating them to smaller entries to minimize
2N/A * wasted space. (Such an implementation should also coalesce adjacent
2N/A * holes to reduce fragmentation). This implementation does neither.
2N/A *
2N/A * There are no separators between fields of an entry.
2N/A * A principal is a length-encoded array of length-encoded strings. The
2N/A * length is a krb5_int16 in each case. The specific format, then, is
2N/A * multiple entries concatinated with no separators. An entry has this
2N/A * exact format:
2N/A *
2N/A * sizeof(krb5_int16) bytes for number of components in the principal;
2N/A * then, each component listed in ordser.
2N/A * For each component, sizeof(krb5_int16) bytes for the number of bytes
2N/A * in the component, followed by the component.
2N/A * sizeof(krb5_int32) for the principal type (for KEYTAB V2 and higher)
2N/A * sizeof(krb5_int32) bytes for the timestamp
2N/A * sizeof(krb5_octet) bytes for the key version number
2N/A * sizeof(krb5_int16) bytes for the enctype
2N/A * sizeof(krb5_int32) bytes for the key length, followed by the key
2N/A */
2N/A
2N/A#ifndef SEEK_SET
2N/A#define SEEK_SET 0
2N/A#define SEEK_CUR 1
2N/A#endif
2N/A
2N/Atypedef krb5_int16 krb5_kt_vno;
2N/A
2N/A#define krb5_kt_default_vno ((krb5_kt_vno)KRB5_KT_DEFAULT_VNO)
2N/A
2N/A#ifdef ANSI_STDIO
2N/A/* Solaris Kerberos */
2N/Astatic char *const fopen_mode_rbplus= "rb+F";
2N/Astatic char *const fopen_mode_rb = "rbF";
2N/A#else
2N/A/* Solaris Kerberos */
2N/Astatic char *const fopen_mode_rbplus= "r+F";
2N/Astatic char *const fopen_mode_rb = "rF";
2N/A#endif
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_open(krb5_context context, krb5_keytab id, int mode)
2N/A{
2N/A krb5_error_code kerror;
2N/A krb5_kt_vno kt_vno;
2N/A int writevno = 0;
2N/A
2N/A KTCHECKLOCK(id);
2N/A errno = 0;
2N/A KTFILEP(id) = fopen(KTFILENAME(id),
2N/A (mode == KRB5_LOCKMODE_EXCLUSIVE) ?
2N/A fopen_mode_rbplus : fopen_mode_rb);
2N/A if (!KTFILEP(id)) {
2N/A if ((mode == KRB5_LOCKMODE_EXCLUSIVE) && (errno == ENOENT)) {
2N/A /* try making it first time around */
2N/A krb5_create_secure_file(context, KTFILENAME(id));
2N/A errno = 0;
2N/A KTFILEP(id) = fopen(KTFILENAME(id), fopen_mode_rbplus);
2N/A if (!KTFILEP(id))
2N/A goto report_errno;
2N/A writevno = 1;
2N/A } else {
2N/A report_errno:
2N/A switch (errno) {
2N/A case 0:
2N/A /* XXX */
2N/A return EMFILE;
2N/A case ENOENT:
2N/A krb5_set_error_message(context, ENOENT,
2N/A /* Solaris Kerberos - added dgettext */
2N/A dgettext(TEXT_DOMAIN,
2N/A "Key table file '%s' not found"),
2N/A KTFILENAME(id));
2N/A return ENOENT;
2N/A default:
2N/A return errno;
2N/A }
2N/A }
2N/A }
2N/A set_cloexec_file(KTFILEP(id));
2N/A if ((kerror = krb5_lock_file(context, fileno(KTFILEP(id)), mode))) {
2N/A (void) fclose(KTFILEP(id));
2N/A KTFILEP(id) = 0;
2N/A return kerror;
2N/A }
2N/A /* assume ANSI or BSD-style stdio */
2N/A setbuf(KTFILEP(id), KTFILEBUFP(id));
2N/A
2N/A /* get the vno and verify it */
2N/A if (writevno) {
2N/A kt_vno = htons(krb5_kt_default_vno);
2N/A KTVERSION(id) = krb5_kt_default_vno;
2N/A if (!fwrite(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
2N/A kerror = errno;
2N/A (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
2N/A (void) fclose(KTFILEP(id));
2N/A KTFILEP(id) = 0;
2N/A return kerror;
2N/A }
2N/A } else {
2N/A /* gotta verify it instead... */
2N/A if (!fread(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
2N/A if (feof(KTFILEP(id)))
2N/A kerror = KRB5_KEYTAB_BADVNO;
2N/A else
2N/A kerror = errno;
2N/A (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
2N/A (void) fclose(KTFILEP(id));
2N/A KTFILEP(id) = 0;
2N/A return kerror;
2N/A }
2N/A kt_vno = KTVERSION(id) = ntohs(kt_vno);
2N/A if ((kt_vno != KRB5_KT_VNO) &&
2N/A (kt_vno != KRB5_KT_VNO_1)) {
2N/A (void) krb5_unlock_file(context, fileno(KTFILEP(id)));
2N/A (void) fclose(KTFILEP(id));
2N/A KTFILEP(id) = 0;
2N/A return KRB5_KEYTAB_BADVNO;
2N/A }
2N/A }
2N/A KTSTARTOFF(id) = ftell(KTFILEP(id));
2N/A return 0;
2N/A}
2N/A
2N/A/* Solaris Kerberos */
2N/Akrb5_error_code
2N/Akrb5_ktfileint_openr(krb5_context context, krb5_keytab id)
2N/A{
2N/A return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_SHARED);
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_openw(krb5_context context, krb5_keytab id)
2N/A{
2N/A return krb5_ktfileint_open(context, id, KRB5_LOCKMODE_EXCLUSIVE);
2N/A}
2N/A
2N/A/* Solaris Kerberos */
2N/Akrb5_error_code
2N/Akrb5_ktfileint_close(krb5_context context, krb5_keytab id)
2N/A{
2N/A krb5_error_code kerror;
2N/A
2N/A KTCHECKLOCK(id);
2N/A if (!KTFILEP(id))
2N/A return 0;
2N/A kerror = krb5_unlock_file(context, fileno(KTFILEP(id)));
2N/A (void) fclose(KTFILEP(id));
2N/A KTFILEP(id) = 0;
2N/A return kerror;
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_delete_entry(krb5_context context, krb5_keytab id, krb5_int32 delete_point)
2N/A{
2N/A krb5_int32 size;
2N/A krb5_int32 len;
2N/A char iobuf[BUFSIZ];
2N/A
2N/A KTCHECKLOCK(id);
2N/A if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
2N/A return errno;
2N/A }
2N/A if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
2N/A return KRB5_KT_END;
2N/A }
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1)
2N/A size = ntohl(size);
2N/A
2N/A if (size > 0) {
2N/A krb5_int32 minus_size = -size;
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1)
2N/A minus_size = htonl(minus_size);
2N/A
2N/A if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
2N/A return errno;
2N/A }
2N/A
2N/A if (!fwrite(&minus_size, sizeof(minus_size), 1, KTFILEP(id))) {
2N/A return KRB5_KT_IOERR;
2N/A }
2N/A
2N/A if (size < BUFSIZ) {
2N/A len = size;
2N/A } else {
2N/A len = BUFSIZ;
2N/A }
2N/A
2N/A memset(iobuf, 0, (size_t) len);
2N/A while (size > 0) {
2N/A if (!fwrite(iobuf, 1, (size_t) len, KTFILEP(id))) {
2N/A return KRB5_KT_IOERR;
2N/A }
2N/A size -= len;
2N/A if (size < len) {
2N/A len = size;
2N/A }
2N/A }
2N/A
2N/A return krb5_sync_disk_file(context, KTFILEP(id));
2N/A }
2N/A
2N/A return 0;
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_internal_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *ret_entry, krb5_int32 *delete_point)
2N/A{
2N/A krb5_octet vno;
2N/A krb5_int16 count;
2N/A unsigned int u_count, u_princ_size;
2N/A krb5_int16 enctype;
2N/A krb5_int16 princ_size;
2N/A register int i;
2N/A krb5_int32 size;
2N/A krb5_int32 start_pos;
2N/A krb5_error_code error;
2N/A char *tmpdata;
2N/A krb5_data *princ;
2N/A
2N/A KTCHECKLOCK(id);
2N/A memset(ret_entry, 0, sizeof(krb5_keytab_entry));
2N/A ret_entry->magic = KV5M_KEYTAB_ENTRY;
2N/A
2N/A /* fseek to synchronise buffered I/O on the key table. */
2N/A
2N/A if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
2N/A {
2N/A return errno;
2N/A }
2N/A
2N/A do {
2N/A *delete_point = ftell(KTFILEP(id));
2N/A if (!fread(&size, sizeof(size), 1, KTFILEP(id))) {
2N/A return KRB5_KT_END;
2N/A }
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1)
2N/A size = ntohl(size);
2N/A
2N/A if (size < 0) {
2N/A if (fseek(KTFILEP(id), -size, SEEK_CUR)) {
2N/A return errno;
2N/A }
2N/A }
2N/A } while (size < 0);
2N/A
2N/A if (size == 0) {
2N/A return KRB5_KT_END;
2N/A }
2N/A
2N/A start_pos = ftell(KTFILEP(id));
2N/A
2N/A /* deal with guts of parsing... */
2N/A
2N/A /* first, int16 with #princ components */
2N/A if (!fread(&count, sizeof(count), 1, KTFILEP(id)))
2N/A return KRB5_KT_END;
2N/A if (KTVERSION(id) == KRB5_KT_VNO_1) {
2N/A count -= 1; /* V1 includes the realm in the count */
2N/A } else {
2N/A count = ntohs(count);
2N/A }
2N/A if (!count || (count < 0))
2N/A return KRB5_KT_END;
2N/A ret_entry->principal = (krb5_principal)malloc(sizeof(krb5_principal_data));
2N/A if (!ret_entry->principal)
2N/A return ENOMEM;
2N/A
2N/A u_count = count;
2N/A ret_entry->principal->magic = KV5M_PRINCIPAL;
2N/A ret_entry->principal->length = u_count;
2N/A ret_entry->principal->data = (krb5_data *)
2N/A calloc(u_count, sizeof(krb5_data));
2N/A if (!ret_entry->principal->data) {
2N/A free(ret_entry->principal);
2N/A ret_entry->principal = 0;
2N/A return ENOMEM;
2N/A }
2N/A
2N/A /* Now, get the realm data */
2N/A if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1)
2N/A princ_size = ntohs(princ_size);
2N/A if (!princ_size || (princ_size < 0)) {
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A u_princ_size = princ_size;
2N/A
2N/A krb5_princ_set_realm_length(context, ret_entry->principal, u_princ_size);
2N/A tmpdata = malloc(u_princ_size+1);
2N/A if (!tmpdata) {
2N/A error = ENOMEM;
2N/A goto fail;
2N/A }
2N/A if (fread(tmpdata, 1, u_princ_size, KTFILEP(id)) != (size_t) princ_size) {
2N/A free(tmpdata);
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A tmpdata[princ_size] = 0; /* Some things might be expecting null */
2N/A /* termination... ``Be conservative in */
2N/A /* what you send out'' */
2N/A krb5_princ_set_realm_data(context, ret_entry->principal, tmpdata);
2N/A
2N/A for (i = 0; i < count; i++) {
2N/A princ = krb5_princ_component(context, ret_entry->principal, i);
2N/A if (!fread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1)
2N/A princ_size = ntohs(princ_size);
2N/A if (!princ_size || (princ_size < 0)) {
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A
2N/A u_princ_size = princ_size;
2N/A princ->length = u_princ_size;
2N/A princ->data = malloc(u_princ_size+1);
2N/A if (!princ->data) {
2N/A error = ENOMEM;
2N/A goto fail;
2N/A }
2N/A
2N/A /* Solaris Kerberos - Allow for empty components */
2N/A if (u_princ_size > 0) {
2N/A if (!fread(princ->data, sizeof(char), u_princ_size, KTFILEP(id))) {
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A }
2N/A princ->data[princ_size] = 0; /* Null terminate */
2N/A }
2N/A
2N/A /* read in the principal type, if we can get it */
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1) {
2N/A if (!fread(&ret_entry->principal->type,
2N/A sizeof(ret_entry->principal->type), 1, KTFILEP(id))) {
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A ret_entry->principal->type = ntohl(ret_entry->principal->type);
2N/A }
2N/A
2N/A /* read in the timestamp */
2N/A if (!fread(&ret_entry->timestamp, sizeof(ret_entry->timestamp), 1, KTFILEP(id))) {
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1)
2N/A ret_entry->timestamp = ntohl(ret_entry->timestamp);
2N/A
2N/A /* read in the version number */
2N/A if (!fread(&vno, sizeof(vno), 1, KTFILEP(id))) {
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A ret_entry->vno = (krb5_kvno)vno;
2N/A
2N/A /* key type */
2N/A if (!fread(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A ret_entry->key.enctype = (krb5_enctype)enctype;
2N/A
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1)
2N/A ret_entry->key.enctype = ntohs(ret_entry->key.enctype);
2N/A
2N/A /* key contents */
2N/A ret_entry->key.magic = KV5M_KEYBLOCK;
2N/A
2N/A if (!fread(&count, sizeof(count), 1, KTFILEP(id))) {
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1)
2N/A count = ntohs(count);
2N/A if (!count || (count < 0)) {
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A
2N/A u_count = count;
2N/A ret_entry->key.length = u_count;
2N/A
2N/A ret_entry->key.contents = (krb5_octet *)malloc(u_count);
2N/A if (!ret_entry->key.contents) {
2N/A error = ENOMEM;
2N/A goto fail;
2N/A }
2N/A if (!fread(ret_entry->key.contents, sizeof(krb5_octet), count,
2N/A KTFILEP(id))) {
2N/A error = KRB5_KT_END;
2N/A goto fail;
2N/A }
2N/A
2N/A /*
2N/A * Reposition file pointer to the next inter-record length field.
2N/A */
2N/A if (fseek(KTFILEP(id), start_pos + size, SEEK_SET) == -1) {
2N/A error = errno;
2N/A goto fail;
2N/A }
2N/A
2N/A return 0;
2N/Afail:
2N/A
2N/A for (i = 0; i < krb5_princ_size(context, ret_entry->principal); i++) {
2N/A princ = krb5_princ_component(context, ret_entry->principal, i);
2N/A if (princ->data)
2N/A free(princ->data);
2N/A }
2N/A free(ret_entry->principal->data);
2N/A ret_entry->principal->data = 0;
2N/A free(ret_entry->principal);
2N/A ret_entry->principal = 0;
2N/A return error;
2N/A}
2N/A
2N/A/* Solaris Kerberos */
2N/Akrb5_error_code
2N/Akrb5_ktfileint_read_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entryp)
2N/A{
2N/A krb5_int32 delete_point;
2N/A
2N/A return krb5_ktfileint_internal_read_entry(context, id, entryp, &delete_point);
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_write_entry(krb5_context context, krb5_keytab id, krb5_keytab_entry *entry)
2N/A{
2N/A krb5_octet vno;
2N/A krb5_data *princ;
2N/A krb5_int16 count, size, enctype;
2N/A krb5_error_code retval = 0;
2N/A krb5_timestamp timestamp;
2N/A krb5_int32 princ_type;
2N/A krb5_int32 size_needed;
2N/A krb5_int32 commit_point = -1;
2N/A int i;
2N/A
2N/A KTCHECKLOCK(id);
2N/A retval = krb5_ktfileint_size_entry(context, entry, &size_needed);
2N/A if (retval)
2N/A return retval;
2N/A retval = krb5_ktfileint_find_slot(context, id, &size_needed, &commit_point);
2N/A if (retval)
2N/A return retval;
2N/A
2N/A /* fseek to synchronise buffered I/O on the key table. */
2N/A /* XXX Without the weird setbuf crock, can we get rid of this now? */
2N/A if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
2N/A {
2N/A return errno;
2N/A }
2N/A
2N/A if (KTVERSION(id) == KRB5_KT_VNO_1) {
2N/A count = (krb5_int16) krb5_princ_size(context, entry->principal) + 1;
2N/A } else {
2N/A count = htons((u_short) krb5_princ_size(context, entry->principal));
2N/A }
2N/A
2N/A if (!fwrite(&count, sizeof(count), 1, KTFILEP(id))) {
2N/A abend:
2N/A return KRB5_KT_IOERR;
2N/A }
2N/A size = krb5_princ_realm(context, entry->principal)->length;
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1)
2N/A size = htons(size);
2N/A if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
2N/A goto abend;
2N/A }
2N/A if (!fwrite(krb5_princ_realm(context, entry->principal)->data, sizeof(char),
2N/A krb5_princ_realm(context, entry->principal)->length, KTFILEP(id))) {
2N/A goto abend;
2N/A }
2N/A
2N/A count = (krb5_int16) krb5_princ_size(context, entry->principal);
2N/A for (i = 0; i < count; i++) {
2N/A princ = krb5_princ_component(context, entry->principal, i);
2N/A size = princ->length;
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1)
2N/A size = htons(size);
2N/A if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
2N/A goto abend;
2N/A }
2N/A /* Solaris Kerberos - Allow for empty components */
2N/A if (princ->length != 0) {
2N/A if (!fwrite(princ->data, sizeof(char), princ->length, KTFILEP(id))) {
2N/A goto abend;
2N/A }
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * Write out the principal type
2N/A */
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1) {
2N/A princ_type = htonl(krb5_princ_type(context, entry->principal));
2N/A if (!fwrite(&princ_type, sizeof(princ_type), 1, KTFILEP(id))) {
2N/A goto abend;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * Fill in the time of day the entry was written to the keytab.
2N/A */
2N/A if (krb5_timeofday(context, &entry->timestamp)) {
2N/A entry->timestamp = 0;
2N/A }
2N/A if (KTVERSION(id) == KRB5_KT_VNO_1)
2N/A timestamp = entry->timestamp;
2N/A else
2N/A timestamp = htonl(entry->timestamp);
2N/A if (!fwrite(&timestamp, sizeof(timestamp), 1, KTFILEP(id))) {
2N/A goto abend;
2N/A }
2N/A
2N/A /* key version number */
2N/A vno = (krb5_octet)entry->vno;
2N/A if (!fwrite(&vno, sizeof(vno), 1, KTFILEP(id))) {
2N/A goto abend;
2N/A }
2N/A /* key type */
2N/A if (KTVERSION(id) == KRB5_KT_VNO_1)
2N/A enctype = entry->key.enctype;
2N/A else
2N/A enctype = htons(entry->key.enctype);
2N/A if (!fwrite(&enctype, sizeof(enctype), 1, KTFILEP(id))) {
2N/A goto abend;
2N/A }
2N/A /* key length */
2N/A if (KTVERSION(id) == KRB5_KT_VNO_1)
2N/A size = entry->key.length;
2N/A else
2N/A size = htons(entry->key.length);
2N/A if (!fwrite(&size, sizeof(size), 1, KTFILEP(id))) {
2N/A goto abend;
2N/A }
2N/A if (!fwrite(entry->key.contents, sizeof(krb5_octet),
2N/A entry->key.length, KTFILEP(id))) {
2N/A goto abend;
2N/A }
2N/A
2N/A if (fflush(KTFILEP(id)))
2N/A goto abend;
2N/A
2N/A retval = krb5_sync_disk_file(context, KTFILEP(id));
2N/A
2N/A if (retval) {
2N/A return retval;
2N/A }
2N/A
2N/A if (fseek(KTFILEP(id), commit_point, SEEK_SET)) {
2N/A return errno;
2N/A }
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1)
2N/A size_needed = htonl(size_needed);
2N/A if (!fwrite(&size_needed, sizeof(size_needed), 1, KTFILEP(id))) {
2N/A goto abend;
2N/A }
2N/A if (fflush(KTFILEP(id)))
2N/A goto abend;
2N/A retval = krb5_sync_disk_file(context, KTFILEP(id));
2N/A
2N/A return retval;
2N/A}
2N/A
2N/A/*
2N/A * Determine the size needed for a file entry for the given
2N/A * keytab entry.
2N/A */
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_size_entry(krb5_context context, krb5_keytab_entry *entry, krb5_int32 *size_needed)
2N/A{
2N/A krb5_int16 count;
2N/A krb5_int32 total_size, i;
2N/A krb5_error_code retval = 0;
2N/A
2N/A count = (krb5_int16) krb5_princ_size(context, entry->principal);
2N/A
2N/A total_size = sizeof(count);
2N/A total_size += krb5_princ_realm(context, entry->principal)->length + (sizeof(krb5_int16));
2N/A
2N/A for (i = 0; i < count; i++) {
2N/A total_size += krb5_princ_component(context, entry->principal,i)->length
2N/A + (sizeof(krb5_int16));
2N/A }
2N/A
2N/A total_size += sizeof(entry->principal->type);
2N/A total_size += sizeof(entry->timestamp);
2N/A total_size += sizeof(krb5_octet);
2N/A total_size += sizeof(krb5_int16);
2N/A total_size += sizeof(krb5_int16) + entry->key.length;
2N/A
2N/A *size_needed = total_size;
2N/A return retval;
2N/A}
2N/A
2N/A/*
2N/A * Find and reserve a slot in the file for an entry of the needed size.
2N/A * The commit point will be set to the position in the file where the
2N/A * the length (sizeof(krb5_int32) bytes) of this node should be written
2N/A * when commiting the write. The file position left as a result of this
2N/A * call is the position where the actual data should be written.
2N/A *
2N/A * The size_needed argument may be adjusted if we find a hole that is
2N/A * larger than the size needed. (Recall that size_needed will be used
2N/A * to commit the write, but that this field must indicate the size of the
2N/A * block in the file rather than the size of the actual entry)
2N/A */
2N/A/*
2N/A * Solaris Kerberos
2N/A * 183 resync - major changes from MIT so not merging in our changes.
2N/A */
2N/Astatic krb5_error_code
2N/Akrb5_ktfileint_find_slot(krb5_context context, krb5_keytab id, krb5_int32 *size_needed, krb5_int32 *commit_point_ptr)
2N/A{
2N/A FILE *fp;
2N/A krb5_int32 size, zero_point, commit_point;
2N/A krb5_kt_vno kt_vno;
2N/A
2N/A KTCHECKLOCK(id);
2N/A fp = KTFILEP(id);
2N/A /* Skip over file version number. */
2N/A if (fseek(fp, 0, SEEK_SET))
2N/A return errno;
2N/A if (!fread(&kt_vno, sizeof(kt_vno), 1, fp))
2N/A return errno;
2N/A
2N/A for (;;) {
2N/A commit_point = ftell(fp);
2N/A if (commit_point == -1)
2N/A return errno;
2N/A if (!fread(&size, sizeof(size), 1, fp)) {
2N/A /* Hit the end of file, reserve this slot. */
2N/A /* Necessary to avoid a later fseek failing on Solaris 10. */
2N/A if (fseek(fp, 0, SEEK_CUR))
2N/A return errno;
2N/A /* htonl(0) is 0, so no need to worry about byte order */
2N/A size = 0;
2N/A if (!fwrite(&size, sizeof(size), 1, fp))
2N/A return errno;
2N/A break;
2N/A }
2N/A
2N/A if (KTVERSION(id) != KRB5_KT_VNO_1)
2N/A size = ntohl(size);
2N/A
2N/A if (size > 0) {
2N/A /* Non-empty record; seek past it. */
2N/A if (fseek(fp, size, SEEK_CUR))
2N/A return errno;
2N/A } else if (size < 0) {
2N/A /* Empty record; use if it's big enough, seek past otherwise. */
2N/A size = -size;
2N/A if (size >= *size_needed) {
2N/A *size_needed = size;
2N/A break;
2N/A } else {
2N/A if (fseek(fp, size, SEEK_CUR))
2N/A return errno;
2N/A }
2N/A } else {
2N/A /* Empty record at end of file; use it. */
2N/A /* Ensure the new record will be followed by another 0. */
2N/A zero_point = ftell(fp);
2N/A if (zero_point == -1)
2N/A return errno;
2N/A if (fseek(fp, *size_needed, SEEK_CUR))
2N/A return errno;
2N/A /* htonl(0) is 0, so no need to worry about byte order */
2N/A if (!fwrite(&size, sizeof(size), 1, fp))
2N/A return errno;
2N/A if (fseek(fp, zero_point, SEEK_SET))
2N/A return errno;
2N/A break;
2N/A }
2N/A }
2N/A
2N/A *commit_point_ptr = commit_point;
2N/A return 0;
2N/A}
2N/A#endif /* LEAN_CLIENT */