Folded in HP's changes to the keytab file format, plus our changes to
authorTheodore Tso <tytso@mit.edu>
Tue, 29 Sep 1992 13:54:43 +0000 (13:54 +0000)
committerTheodore Tso <tytso@mit.edu>
Tue, 29 Sep 1992 13:54:43 +0000 (13:54 +0000)
handle the principal type storage.

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@2423 dc483132-0cff-0310-8789-dd5450dbe970

src/lib/krb5/keytab/file/ktf_util.c

index b8907bfa1caf7610861f7d760406f97b3cc8d446..b97a3d105daab4d172f61bb1f6d02734ddc0ccf7 100644 (file)
@@ -2,6 +2,10 @@
  * $Source$
  * $Author$
  *
+ * Copyright (c) Hewlett-Packard Company 1991
+ * Released to the Massachusetts Institute of Technology for inclusion
+ * in the Kerberos source code distribution.
+ *
  * Copyright 1990,1991 by the Massachusetts Institute of Technology.
  * All Rights Reserved.
  *
  * The format is as follows:
  * 
  * <file format vno>
- * principal vno key
- * principal vno key
+ * <record length>
+ * principal timestamp vno key
+ * <record length>
+ * principal timestamp vno key
  * ....
  *
- * There are no separators between fields of an entry or between entries.
+ * A length field (sizeof(krb5_int32)) exists between entries.  When this
+ * length is positive it indicates an active entry, when negative a hole.
+ * The length indicates the size of the block in the file (this may be 
+ * larger than the size of the next record, since we are using a first
+ * fit algorithm for re-using holes and the first fit may be larger than
+ * the entry we are writing).  Another (compatible) implementation could
+ * break up holes when allocating them to smaller entries to minimize 
+ * wasted space.  (Such an implementation should also coalesce adjacent
+ * holes to reduce fragmentation).  This implementation does neither.
+ *
+ * There are no separators between fields of an entry.  
  * A principal is a length-encoded array of length-encoded strings.  The
  * length is a krb5_int16 in each case.  The specific format, then, is 
  * multiple entries concatinated with no separators.  An entry has this 
  * then, each component listed in ordser.
  * For each component, sizeof(krb5_int16) bytes for the number of bytes
  * in the component, followed by the component.
+ * sizeof(krb5_int32) for the principal type (for KEYTAB V2 and higher)
+ * sizeof(krb5_timestamp) bytes for the timestamp
  * sizeof(krb5_kvno) bytes for the key version number
  * sizeof(krb5_keytype) bytes for the keytype
  * sizeof(krb5_int32) bytes for the key length, followed by the key
  *
- * Extra garbage at the end of a keytab will be not be searched for, but
- * 
- * 
  */
 
 #if !defined(lint) && !defined(SABER)
@@ -63,13 +78,31 @@ static char rcsid_ktf_util_c[] =
 #include <krb5/ext-proto.h>
 #include <krb5/libos.h>
 #include <krb5/los-proto.h>
+#include <krb5/osconf.h>
+#include <netinet/in.h>
+#include <stdio.h>
 
 #include "ktfile.h"
-#include <krb5/osconf.h>
 
-/* keytab version 1 didn't do byte swapping correctly; call this version 2
-   so old files will be recognized as old instead of badly formatted. */
-#define KRB5_KT_VNO    0x0502          /* krb5, keytab v 2 */
+#ifndef SEEK_SET
+#define SEEK_SET 0
+#define SEEK_CUR 1
+#endif
+
+typedef krb5_int16  krb5_kt_vno;
+
+krb5_kt_vno krb5_kt_default_vno = KRB5_KT_DEFAULT_VNO;
+
+#define xfwrite(a, b, c, d) fwrite((char *)a, b, c, d)
+#define xfread(a, b, c, d) fread((char *)a, b, c, d)
+
+#ifdef ANSI_STDIO
+static char *fopen_mode_rbplus= "rb+";
+static char *fopen_mode_rb = "rb";
+#else
+static char *fopen_mode_rbplus= "r+";
+static char *fopen_mode_rb = "r";
+#endif
 
 extern int errno;
 
@@ -78,87 +111,59 @@ krb5_ktfileint_open(id, mode)
 krb5_keytab id;
 int mode;
 {
-    register FILE *fp;
     krb5_error_code kerror;
+    krb5_kt_vno kt_vno;
     int writevno = 0;
-#ifdef POSIX_TYPES
-    mode_t omask;
-#else
-    int omask;
-#endif
-
-    /* Make sure nobody else can read the new file.  It might be better
-       to use open with mode 600 followed by fdopen on UNIX systems.  */
-    omask = umask(066);
 
-#ifdef ANSI_STDIO
-    fp = fopen(KTFILENAME(id),
-              (mode == KRB5_LOCKMODE_EXCLUSIVE) ? "rb+" : "rb");
-#else
-    fp = fopen(KTFILENAME(id),
-              (mode == KRB5_LOCKMODE_EXCLUSIVE) ? "r+" : "r");
-#endif
-    if (!fp) {
+    KTFILEP(id) = fopen(KTFILENAME(id),
+                       (mode == KRB5_LOCKMODE_EXCLUSIVE) ?
+                         fopen_mode_rbplus : fopen_mode_rb);
+    if (!KTFILEP(id)) {
        if ((mode == KRB5_LOCKMODE_EXCLUSIVE) && (errno == ENOENT)) {
            /* try making it first time around */
-#ifdef ANSI_STDIO
-           fp = fopen(KTFILENAME(id), "ab+");
-#else
-           fp = fopen(KTFILENAME(id), "a+");
-#endif
-           if (!fp) {
-               (void) umask (omask);
+            krb5_create_secure_file(KTFILENAME(id));
+           KTFILEP(id) = fopen(KTFILENAME(id), fopen_mode_rbplus);
+           if (!KTFILEP(id))
                return errno;
-           }
            writevno = 1;
-       } else {                        /* some other error */
-           (void) umask (omask);
+       } else                          /* some other error */
            return errno;
-       }
     }
-    (void) umask (omask);
-
-    if (kerror = krb5_lock_file(fp, KTFILENAME(id), mode)) {
-       (void) fclose(fp);
+    if (kerror = krb5_lock_file(KTFILEP(id), KTFILENAME(id),
+                               mode)) {
+       (void) fclose(KTFILEP(id));
+       KTFILEP(id) = 0;
        return kerror;
     }
+    /* assume ANSI or BSD-style stdio */
+    setbuf(KTFILEP(id), NULL);
 
     /* get the vno and verify it */
     if (writevno) {
-       /* Write a version number, MSB first. */
-       if (putc((KRB5_KT_VNO >> 8), fp) == EOF || putc(KRB5_KT_VNO, fp) == EOF) {
-           (void) krb5_unlock_file(fp, KTFILENAME(id));
-           (void) fclose(fp);
-           return KRB5_KT_IOERR;
+       kt_vno = htons(krb5_kt_default_vno);
+       KTVERSION(id) = krb5_kt_default_vno;
+       if (!xfwrite(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
+           kerror = errno;
+           (void) krb5_unlock_file(KTFILEP(id), KTFILENAME(id));
+           (void) fclose(KTFILEP(id));
+           return kerror;
        }
     } else {
-       int c1, c2;
-
-       /* Verify version number. */
-       c1 = getc(fp);
-       c2 = getc(fp);
-
-       if (c1 == EOF || c2 == EOF) {
-           kerror = feof(fp) ? KRB5_KT_END : KRB5_KT_IOERR;
-           (void) krb5_unlock_file(fp, KTFILENAME(id));
-           (void) fclose(fp);
+       /* gotta verify it instead... */
+       if (!xfread(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
+           kerror = errno;
+           (void) krb5_unlock_file(KTFILEP(id), KTFILENAME(id));
+           (void) fclose(KTFILEP(id));
            return kerror;
        }
-       if ((c1 << 8) + c2 != KRB5_KT_VNO) {
-           (void) krb5_unlock_file(fp, KTFILENAME(id));
-           (void) fclose(fp);
-           return KRB5_KEYTAB_BADVNO;
-       }
-    }
-    /* seek to the end for writers */
-    if (mode == KRB5_LOCKMODE_EXCLUSIVE) {
-       if (fseek(fp, 0, 2)) {
-           (void) krb5_unlock_file(fp, KTFILENAME(id));
-           (void) fclose(fp);
-           return KRB5_KT_IOERR;
+       kt_vno = KTVERSION(id) = ntohs(kt_vno);
+       if ((kt_vno != KRB5_KT_VNO) &&
+           (kt_vno != KRB5_KT_VNO_1)) {
+           (void) krb5_unlock_file(KTFILEP(id), KTFILENAME(id));
+           (void) fclose(KTFILEP(id));
+           return -1 /* KRB5_KEYTAB_BADVNO */;
        }
     }
-    KTFILEP(id) = fp;
     return 0;
 }
 
@@ -190,241 +195,564 @@ krb5_keytab id;
     return kerror;
 }
 
-/* Keytab file format.  This is not documented anywhere and is not known
-   outside this file.
-
-   Each entry in the file contains:
+krb5_error_code
+krb5_ktfileint_delete_entry(id, delete_point)
+krb5_keytab id;
+krb5_int32 delete_point;
+{
+    krb5_int32  size;
+    krb5_int32  len;
+    char        iobuf[BUFSIZ];
 
-   prinicipal name:
-     2 byte count of number of components in name
-        component:
-         2 byte count of number of bytes
-         data
-   1 byte key version number
-   2 byte key type
-   4 byte key length
-   key data
+    if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
+        return errno;
+    }
+    if (!xfread(&size, sizeof(size), 1, KTFILEP(id))) {
+        return KRB5_KT_END;
+    }
+    if (size > 0) {
+        size = -size;
+        if (fseek(KTFILEP(id), delete_point, SEEK_SET)) {
+            return errno;
+        }
+        if (!xfwrite(&size, sizeof(size), 1, KTFILEP(id))) {
+            return KRB5_KT_IOERR;
+        }
+
+        size = -size;
+        if (size < BUFSIZ) {
+            len = size;
+        } else {
+            len = BUFSIZ;
+        }
+
+        memset(iobuf, 0, len);
+        while (size > 0) {
+            xfwrite(iobuf, 1, len, KTFILEP(id));
+            size -= len;
+            if (size < len) {
+                len = size;
+            }
+        }
+
+        return krb5_sync_disk_file(KTFILEP(id));
+    }
 
-   The write file function could do range checking on 2 byte quantities,
-   but doesn't.  Values greater than 2 ^ 15 are unlikely.
-*/
+    return 0;
+}
 
 krb5_error_code
-krb5_ktfileint_read_entry(id, entryp)
+krb5_ktfileint_internal_read_entry(id, entrypp, delete_point)
 krb5_keytab id;
-krb5_keytab_entry **entryp;
+krb5_keytab_entry **entrypp;
+krb5_int32 *delete_point;
 {
-  krb5_keytab_entry *entry;
-  register FILE *fp = KTFILEP(id);
-  int i;       /* index into principal component array; failure cleanup
-                  code uses this to determine how much to free */
-  int count;
-  int size;
-  int c1, c2;
-  krb5_error_code error;
-
-  entry = (krb5_keytab_entry *)malloc (sizeof (krb5_keytab_entry));
-  if (entry == 0)
-    return ENOMEM;
-
-  /* Read a character at a time to avoid any problems with byte order. */
-  c1 = getc(fp);
-  c2 = getc(fp);
-  if (c1 == EOF || c2 == EOF)
-    return KRB5_KT_END;
-
-  count = (c1 << 8) + c2;
-
-  if (!(entry->principal = (krb5_principal)malloc(sizeof(krb5_principal_data))))
-    return ENOMEM;
-  if (!(entry->principal->data = (krb5_data *)malloc(count * sizeof(krb5_data))))
+    register krb5_keytab_entry *ret_entry;
+    krb5_int16 count;
+    krb5_int16 princ_size;
+    register int i;
+    krb5_int32 size;
+    krb5_int32 start_pos;
+    krb5_error_code error;
+    char       *tmpdata;
+    krb5_data  *princ;
+
+    if (!(ret_entry = (krb5_keytab_entry *)calloc(1, sizeof(*ret_entry))))
+       return ENOMEM;
+
+    /* fseek to synchronise buffered I/O on the key table. */
+
+    if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
     {
-      free((char *)entry->principal);
-      return ENOMEM;
+        return errno;
     }
-  entry->principal->length = count;
-
-  {
-    char *tmpdata;
 
-    c1 = getc(fp);
-    c2 = getc(fp);
-    if (c1 == EOF || c2 == EOF)
-      {
-       error = KRB5_KT_END;
-       goto fail;
-      }
-      size = (c1 << 8) + c2;
-    krb5_princ_set_realm_length(entry->principal, size);
-    if ((tmpdata = malloc (size)) == 0)
-       {
-         error = ENOMEM;
-         goto fail;
-       }
-    if (fread(tmpdata, 1, size, fp) != size)
-      {
-       free (tmpdata);
-       error = KRB5_KT_END;
-       goto fail;
-      }
-    krb5_princ_set_realm_data(entry->principal, tmpdata);
-  }
+    do {
+        *delete_point = ftell(KTFILEP(id));
+        if (!xfread(&size, sizeof(size), 1, KTFILEP(id))) {
+            return KRB5_KT_END;
+        }
+       if (KTVERSION(id) != KRB5_KT_VNO_1)
+               size = ntohl(size);
+
+        if (size < 0) {
+            if (fseek(KTFILEP(id), -size, SEEK_CUR)) {
+                return errno;
+            }
+        }
+    } while (size < 0);
+
+    if (size == 0) {
+        return KRB5_KT_END;
+    }
 
-  for (i = 0; i < count; i++)
-    {
-      krb5_data *princ = krb5_princ_component(entry->principal, i);
-
-      c1 = getc(fp);
-      c2 = getc(fp);
-      if (c1 == EOF || c2 == EOF)
-       {
-         error = KRB5_KT_END;
-         goto fail;
-       }
+    start_pos = ftell(KTFILEP(id));
 
-      size = (c1 << 8) + c2;
+    /* deal with guts of parsing... */
 
-      princ->length = size;
-      if ((princ->data = malloc (size)) == 0)
-       {
-         error = ENOMEM;
-         goto fail;
-       }
-      if (fread(princ->data, 1, size, fp) != size)
-       {
-         free (princ->data);
-         error = KRB5_KT_END;
-         goto fail;
-       }
+    /* first, int16 with #princ components */
+    if (!xfread(&count, sizeof(count), 1, KTFILEP(id)))
+       return KRB5_KT_END;
+    if (KTVERSION(id) == KRB5_KT_VNO_1) {
+           count -= 1;         /* V1 includes the realm in the count */
+    } else {
+           count = ntohs(count);
+    }
+    if (!count || (count < 0))
+       return KRB5_KT_END;
+    ret_entry->principal = (krb5_principal)malloc(sizeof(krb5_principal_data));
+    if (!ret_entry->principal)
+        return ENOMEM;
+    
+    ret_entry->principal->length = count;
+    ret_entry->principal->data = (krb5_data *)calloc(count, sizeof(krb5_data));
+    if (!ret_entry->principal->data) {
+       free(ret_entry->principal);    
+       return ENOMEM;
     }
 
-  /* key version number: 1 byte */
-  c1 = getc(fp);
-  if (c1 == EOF)
-    {
-      error = KRB5_KT_END;
-      goto fail;
-    }
-  entry->vno = c1;
-  /* keyblock: keytype (2), length (4), contents */
-  c1 = getc(fp);
-  c2 = getc(fp);
-  if (c1 == EOF || c2 == EOF)
-    {
-      error = KRB5_KT_END;
-      goto fail;
+    /* Now, get the realm data */
+    if (!xfread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
+           error = KRB5_KT_END;
+           goto fail;
     }
-  entry->key.keytype = (c1 << 8) | c2;
-  c1 = getc(fp);
-  c2 = getc(fp);
-  if (c1 == EOF || c2 == EOF)
-    {
-      error = KRB5_KT_END;
-      goto fail;
+    if (KTVERSION(id) != KRB5_KT_VNO_1)
+           princ_size = ntohs(princ_size);
+    if (!princ_size || (princ_size < 0)) {
+           error = KRB5_KT_END;
+           goto fail;
     }
-  size = (c1 << 24) + (c2 << 16);
-  c1 = getc(fp);
-  c2 = getc(fp);
-  if (c1 == EOF || c2 == EOF)
-    {
-      error = KRB5_KT_END;
-      goto fail;
+    krb5_princ_set_realm_length(ret_entry->principal, princ_size);
+    tmpdata = malloc(princ_size+1);
+    if (!tmpdata) {
+           error = ENOMEM;
+           goto fail;
     }
-  size += (c1 << 8) + c2;
-
-  entry->key.length = size;
-  if ((entry->key.contents = (krb5_octet *)malloc(size)) == 0)
-    {
-      error = ENOMEM;
-      goto fail;
+    if (fread(tmpdata, 1, princ_size, KTFILEP(id)) != princ_size) {
+           free(tmpdata);
+           error = KRB5_KT_END;
+           goto fail;
+    }
+    tmpdata[princ_size] = 0;   /* Some things might be expecting null */
+                               /* termination...  ``Be conservative in */
+                               /* what you send out'' */
+    krb5_princ_set_realm_data(ret_entry->principal, tmpdata);
+    
+    for (i = 0; i < count; i++) {
+       princ = krb5_princ_component(ret_entry->principal, i);
+       if (!xfread(&princ_size, sizeof(princ_size), 1, KTFILEP(id))) {
+           error = KRB5_KT_END;
+           goto fail;
+        }
+       if (KTVERSION(id) != KRB5_KT_VNO_1)
+           princ_size = ntohs(princ_size);
+       if (!princ_size || (princ_size < 0)) {
+           error = KRB5_KT_END;
+           goto fail;
+        }
+
+       princ->length = princ_size;
+       princ->data = malloc(princ_size+1);
+       if (!princ->data) {
+           error = ENOMEM;
+           goto fail;
+        }
+       if (!xfread(princ->data, sizeof(char), princ_size, KTFILEP(id))) {
+           error = KRB5_KT_END;
+           goto fail;
+        }
+       princ->data[princ_size] = 0; /* Null terminate */
     }
 
-  if (fread((char *)entry->key.contents, 1, size, fp) != size)
-    {
-      free(entry->key.contents);
-      error = KRB5_KT_END;
-      goto fail;
+    /* read in the principal type, if we can get it */
+    if (KTVERSION(id) != KRB5_KT_VNO_1) {
+           if (!xfread(&ret_entry->principal->type,
+                       sizeof(ret_entry->principal->type), 1, KTFILEP(id))) {
+                   error = KRB5_KT_END;
+                   goto fail;
+           }
+           ret_entry->principal->type = ntohl(ret_entry->principal->type);
+    }
+    
+    /* read in the timestamp */
+    if (!xfread(&ret_entry->timestamp, sizeof(ret_entry->timestamp), 1, KTFILEP(id))) {
+       error = KRB5_KT_END;
+       goto fail;
     }
-  *entryp = entry;
-  return 0;
+    if (KTVERSION(id) != KRB5_KT_VNO_1)
+       ret_entry->timestamp = ntohl(ret_entry->timestamp);
+    
+    /* read in the version number */
+    if (!xfread(&ret_entry->vno, sizeof(ret_entry->vno), 1, KTFILEP(id))) {
+       error = KRB5_KT_END;
+       goto fail;
+    }
+    
+    /* key type */
+    if (!xfread(&ret_entry->key.keytype, sizeof(ret_entry->key.keytype), 1,
+               KTFILEP(id))) {
+       error = KRB5_KT_END;
+       goto fail;
+    }
+    if (KTVERSION(id) != KRB5_KT_VNO_1)
+       ret_entry->key.keytype = ntohs(ret_entry->key.keytype);
+    
+    /* key contents */
+    if (!xfread(&count, sizeof(count), 1, KTFILEP(id))) {
+       error = KRB5_KT_END;
+       goto fail;
+    }
+    if (KTVERSION(id) != KRB5_KT_VNO_1)
+       count = ntohs(count);
+    if (!count || (count < 0)) {
+       error = KRB5_KT_END;
+       goto fail;
+    }
+    ret_entry->key.length = count;
+    
+    ret_entry->key.contents = (krb5_octet *)malloc(count);
+    if (!ret_entry->key.contents) {
+       error = ENOMEM;
+       goto fail;
+    }          
+    if (!xfread(ret_entry->key.contents, sizeof(krb5_octet), count,
+               KTFILEP(id))) {
+       error = KRB5_KT_END;
+       goto fail;
+    }
+
+    *entrypp = ret_entry;
 
- fail:
-  free((char *)entry->principal->data);
-  free((char *)entry->principal);
-  return error;
+    /*
+     * Reposition file pointer to the next inter-record length field.
+     */
+    fseek(KTFILEP(id), start_pos + size, SEEK_SET);
+    return 0;
+fail:
+    
+    for (i = 0; i < ret_entry->principal->length; i++) {
+           princ = krb5_princ_component(ret_entry->principal, i);
+           if (princ->data)
+                   free(princ->data);
+    }
+    free(ret_entry->principal->data);
+    free(ret_entry->principal);
+    return error;
 }
 
 krb5_error_code
-krb5_ktfileint_write_entry(id, entry)
+krb5_ktfileint_read_entry(id, entrypp)
 krb5_keytab id;
-register krb5_keytab_entry *entry;
+krb5_keytab_entry **entrypp;
 {
-  register FILE *fp = KTFILEP(id);
-  int count, size;
-  unsigned char c1, c2;
-  register int i;
-
-  /* Do all I/O and check for error once at the end.  This function isn't
-     expensive, and errors should be rare. */
-
-  count = krb5_princ_size(entry->principal);
-
-  /* 2 byte count of number of components in name, MSB first. */
+    krb5_int32 delete_point;
 
-  c2 = count;
-  c1 = count >> 8;
-
-  putc(c1, fp);
-  putc(c2, fp);
+    return krb5_ktfileint_internal_read_entry(id, entrypp, &delete_point);
+}
 
+krb5_error_code
+krb5_ktfileint_write_entry(id, entry)
+krb5_keytab id;
+krb5_keytab_entry *entry;
+{
+    krb5_data *princ;
+    krb5_int16 count, size, keytype;
+    krb5_error_code retval = 0;
+    krb5_timestamp timestamp;
+    krb5_int32 princ_type;
+    krb5_int32  size_needed;
+    krb5_int32  commit_point;
+    int                i;
+    char iobuf[BUFSIZ];
+
+    retval = krb5_ktfileint_size_entry(entry, &size_needed);
+    if (retval)
+        return retval;
+    retval = krb5_ktfileint_find_slot(id, &size_needed, &commit_point);
+    if (retval)
+        return retval;
+
+    setbuf(KTFILEP(id), iobuf);
+
+    /* fseek to synchronise buffered I/O on the key table. */
+
+    if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
     {
-      size = krb5_princ_realm(entry->principal)->length;
-
-      c2 = size;
-      c1 = size >> 8;
+        return errno;
+    }
 
-      putc(c1, fp);
-      putc(c2, fp);
+    if (KTVERSION(id) == KRB5_KT_VNO_1) {
+           count = entry->principal->length + 1;
+    } else {
+           count = htons(entry->principal->length);
+    }
+    
+    if (!xfwrite(&count, sizeof(count), 1, KTFILEP(id))) {
+    abend:
+       setbuf(KTFILEP(id), 0);
+       return KRB5_KT_IOERR;
+    }
+    size = krb5_princ_realm(entry->principal)->length;
+    if (KTVERSION(id) != KRB5_KT_VNO_1)
+           size = htons(size);
+    if (!xfwrite(&size, sizeof(size), 1, KTFILEP(id))) {
+           goto abend;
+    }
+    if (!xfwrite(krb5_princ_realm(entry->principal)->data, sizeof(char),
+                krb5_princ_realm(entry->principal)->length, KTFILEP(id))) {
+           goto abend;
+    }
 
-      fwrite(krb5_princ_realm(entry->principal)->data, 1, size, fp);
+    count = entry->principal->length;
+    for (i = 0; i < count; i++) {
+       princ = krb5_princ_component(entry->principal, i);
+       size = princ->length;
+       if (KTVERSION(id) != KRB5_KT_VNO_1)
+               size = htons(size);
+       if (!xfwrite(&size, sizeof(size), 1, KTFILEP(id))) {
+           goto abend;
+       }
+       if (!xfwrite(princ->data, sizeof(char), princ->length, KTFILEP(id))) {
+           goto abend;
+       }
     }
 
-  for (i = 0; i < count; i++)
-    {
-      size = krb5_princ_component(entry->principal, i)->length;
+    /*
+     * Write out the principal type
+     */
+    if (KTVERSION(id) != KRB5_KT_VNO_1) {
+           princ_type = htonl(krb5_princ_type(entry->principal));
+           if (!xfwrite(&princ_type, sizeof(princ_type), 1, KTFILEP(id))) {
+                   goto abend;
+           }
+    }
+    
+    /*
+     * Fill in the time of day the entry was written to the keytab.
+     */
+    if (krb5_timeofday(&entry->timestamp)) {
+        entry->timestamp = 0;
+    }
+    if (KTVERSION(id) == KRB5_KT_VNO_1)
+           timestamp = entry->timestamp;
+    else
+           timestamp = htonl(entry->timestamp);
+    if (!xfwrite(&timestamp, sizeof(timestamp), 1, KTFILEP(id))) {
+       goto abend;
+    }
+    
+    /* key version number */
+    if (!xfwrite(&entry->vno, sizeof(entry->vno), 1, KTFILEP(id))) {
+       goto abend;
+    }
+    /* key type */
+    if (KTVERSION(id) == KRB5_KT_VNO_1)
+           keytype = entry->key.keytype;
+    else
+           keytype = htons(entry->key.keytype);
+    if (!xfwrite(&keytype, sizeof(keytype), 1, KTFILEP(id))) {
+       goto abend;
+    }
+    /* key length */
+    if (KTVERSION(id) == KRB5_KT_VNO_1)
+           size = entry->key.length;
+    else
+           size = htons(entry->key.length);
+    if (!xfwrite(&size, sizeof(size), 1, KTFILEP(id))) {
+       goto abend;
+    }
+    if (!xfwrite(entry->key.contents, sizeof(krb5_octet),
+                entry->key.length, KTFILEP(id))) {
+       memset(iobuf, 0, sizeof(iobuf));
+       setbuf(KTFILEP(id), 0);
+       return KRB5_KT_IOERR;
+    }  
+
+    retval = krb5_sync_disk_file(KTFILEP(id));
+    (void) memset(iobuf, 0, sizeof(iobuf));
+    setbuf(KTFILEP(id), 0);
+
+    if (retval) {
+        return retval;
+    }
 
-      c2 = size;
-      c1 = size >> 8;
+    if (fseek(KTFILEP(id), commit_point, SEEK_SET)) {
+        return errno;
+    }
+    if (KTVERSION(id) != KRB5_KT_VNO_1)
+           size_needed = htonl(size_needed);
+    if (!xfwrite(&size_needed, sizeof(size_needed), 1, KTFILEP(id))) {
+        goto abend;
+    }
+    retval = krb5_sync_disk_file(KTFILEP(id));
 
-      putc(c1, fp);
-      putc(c2, fp);
+    return retval;
+}
 
-      fwrite(krb5_princ_component(entry->principal, i)->data, 1, size, fp);
+/*
+ * Determine the size needed for a file entry for the given
+ * keytab entry.
+ */
+krb5_error_code
+krb5_ktfileint_size_entry(entry, size_needed)
+krb5_keytab_entry *entry;
+krb5_int32 *size_needed;
+{
+    krb5_int16 count, size;
+    krb5_int32 total_size, i;
+    krb5_error_code retval = 0;
+
+    count = entry->principal->length;
+
+    total_size = sizeof(count);
+    total_size += krb5_princ_realm(entry->principal)->length + (sizeof(size));
+    
+    for (i = 0; i < count; i++) {
+           total_size += krb5_princ_component(entry->principal,i)->length
+                   + (sizeof(size));
     }
-  /* Version number is one byte. */
-  putc(entry->vno, fp);
 
-  /* Key type is 2 bytes. */
-  c2 = entry->key.keytype;
-  c1 = entry->key.keytype >> 8;
+    total_size += sizeof(entry->principal->type);
+    total_size += sizeof(entry->timestamp);
+    total_size += sizeof(entry->vno);
+    total_size += sizeof(entry->key.keytype);
+    total_size += sizeof(size) + entry->key.length;
 
-  putc(c1, fp);
-  putc(c2, fp);
-
-  size = entry->key.length;
+    *size_needed = total_size;
+    return retval;
+}
 
-  c1 = size >> 24;
-  c2 = size >> 16;
-  putc(c1, fp);
-  putc(c2, fp);
-  c1 = size >> 8;
-  c2 = size;
-  putc(c1, fp);
-  putc(c2, fp);
+/*
+ * Find and reserve a slot in the file for an entry of the needed size.
+ * The commit point will be set to the position in the file where the
+ * the length (sizeof(krb5_int32) bytes) of this node should be written
+ * when commiting the write.  The file position left as a result of this
+ * call is the position where the actual data should be written.
+ *
+ * The size_needed argument may be adjusted if we find a hole that is
+ * larger than the size needed.  (Recall that size_needed will be used
+ * to commit the write, but that this field must indicate the size of the
+ * block in the file rather than the size of the actual entry)  
+ */
+krb5_error_code
+krb5_ktfileint_find_slot(id, size_needed, commit_point)
+krb5_keytab id;
+krb5_int32 *size_needed;
+krb5_int32 *commit_point;
+{
+    krb5_int32      size;
+    krb5_int32      remainder;
+    krb5_int32      zero_point;
+    krb5_kt_vno     kt_vno;
+    krb5_boolean    found = FALSE;
+    char            iobuf[BUFSIZ];
+
+    /*
+     * Skip over file version number
+     */
+    if (fseek(KTFILEP(id), 0, SEEK_SET)) {
+        return errno;
+    }
+    if (!xfread(&kt_vno, sizeof(kt_vno), 1, KTFILEP(id))) {
+        return KRB5_KT_IOERR;
+    }
 
-  fwrite((char *)entry->key.contents, 1, size, fp);
+    while (!found) {
+        *commit_point = ftell(KTFILEP(id));
+        if (!xfread(&size, sizeof(size), 1, KTFILEP(id))) {
+            /*
+             * Hit the end of file, reserve this slot.
+             */
+            setbuf(KTFILEP(id), 0);
+            size = 0;
+
+            /* fseek to synchronise buffered I/O on the key table. */
+
+            if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
+            {
+                return errno;
+            }
+           
+#ifdef notdef
+           /* We don't have to do this because htonl(0) == 0 */
+           if (KTVERSION(id) != KRB5_KT_VNO_1)
+                   size = htonl(size);
+#endif
+           
+            if (!xfwrite(&size, sizeof(size), 1, KTFILEP(id))) {
+            abend:
+                return KRB5_KT_IOERR;
+            }
+            found = TRUE;
+        }
+
+       if (KTVERSION(id) != KRB5_KT_VNO_1)
+               size = ntohl(size);
+
+        if (size > 0) {
+            if (fseek(KTFILEP(id), size, SEEK_CUR)) {
+                return errno;
+            }
+        } else if (!found) {
+            size = -size;
+            if (size >= *size_needed) {
+                *size_needed = size;
+                found = TRUE;  
+            } else if (size > 0) {
+                /*
+                 * The current hole is not large enough, so skip it
+                 */
+                if (fseek(KTFILEP(id), size, SEEK_CUR)) {
+                    return errno;
+                }
+            } else {
+
+                 /* fseek to synchronise buffered I/O on the key table. */
+
+                 if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
+                 {
+                     return errno;
+                 }
+
+                /*
+                 * Found the end of the file (marked by a 0 length buffer)
+                 * Make sure we zero any trailing data.
+                 */
+                zero_point = ftell(KTFILEP(id));
+                setbuf(KTFILEP(id), iobuf);
+                while (size = xfread(iobuf, 1, sizeof(iobuf), KTFILEP(id))) {
+                    if (size != sizeof(iobuf)) {
+                        remainder = size % sizeof(krb5_int32);
+                        if (remainder) {
+                            size += sizeof(krb5_int32) - remainder;
+                        }
+                    }
+
+                    if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
+                    {
+                        return errno;
+                    }
+
+                    memset(iobuf, 0, size);
+                    xfwrite(iobuf, 1, size, KTFILEP(id));
+                    if (feof(KTFILEP(id))) {
+                        break;
+                    }
+
+                    if (fseek(KTFILEP(id), 0L, SEEK_CUR) < 0)
+                    {
+                        return errno;
+                    }
+
+                }
+                setbuf(KTFILEP(id), 0);
+                if (fseek(KTFILEP(id), zero_point, SEEK_SET)) {
+                    return errno;
+                }
+            }
+        }
+    }
 
-  if (fflush(fp) == EOF || ferror(fp))
-    return KRB5_KT_IOERR;
-  return 0;
+    return 0;
 }
+