Major key-management revision.
[u/mdw/catacomb] / key-file.c
diff --git a/key-file.c b/key-file.c
new file mode 100644 (file)
index 0000000..ab620e7
--- /dev/null
@@ -0,0 +1,327 @@
+/* -*-c-*-
+ *
+ * $Id: key-file.c,v 1.1 1999/12/22 15:47:48 mdw Exp $
+ *
+ * System-dependent key filing operations
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Catacomb.
+ *
+ * Catacomb is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ * 
+ * Catacomb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with Catacomb; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: key-file.c,v $
+ * Revision 1.1  1999/12/22 15:47:48  mdw
+ * Major key-management revision.
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <mLib/dstr.h>
+#include <mLib/lock.h>
+
+#include "key.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @fdcopy@ --- *
+ *
+ * Arguments:  @int source@ = source file descriptor
+ *             @int dest@ = destination file descriptor
+ *
+ * Returns:    Zero if OK, nonzero otherwise.
+ *
+ * Use:                Copies data from one file descriptor to another.
+ */
+
+static int fdcopy(int source, int dest)
+{
+  char buf[4096];
+
+  if (lseek(source, 0, SEEK_SET) < 0||
+      lseek(dest, 0, SEEK_SET) < 0 ||
+      ftruncate(dest, 0) < 0)
+    return (-1);
+  for (;;) {
+    int n = read(source, buf, sizeof(buf));
+    if (n < 0)
+      return (-1);
+    else if (n == 0)
+      break;
+    else if (write(dest, buf, n) < 0)
+      return (-1);
+  }
+  return (0);
+}
+
+/* --- @key_save@ --- *
+ *
+ * Arguments:  @key_file *f@ = pointer to key file block
+ *
+ * Returns:    A @KWRITE_@ code indicating how well it worked.
+ *
+ * Use:                Writes a key file's data back to the actual file.  This code
+ *             is extremely careful about error handling.  It should usually
+ *             be able to back out somewhere sensible, but it can tell when
+ *             it's got itself into a real pickle and starts leaving well
+ *             alone.
+ *
+ *             Callers, please make sure that you ring alarm bells when this
+ *             function returns @KWRITE_BROKEN@.
+ */
+
+int key_save(key_file *f)
+{
+  dstr n_older = DSTR_INIT, n_old = DSTR_INIT, n_new = DSTR_INIT;
+  int rc = KWRITE_FAIL;
+
+  if (!(f->f & KF_MODIFIED))
+    return (KWRITE_OK);
+
+  /* --- Write a new key file out --- *
+   *
+   * Check for an error after each key line.  This ought to be enough.
+   * Checking after each individual byte write and @fprintf@ isn't much fun.
+   */
+
+  dstr_putf(&n_new, "%s.new", f->name);
+
+  {
+    key *k;
+    key_iter i;
+    FILE *fp;
+
+    if ((fp = fopen(n_new.buf, "w")) == 0)
+      goto fail_open;
+
+    for (key_mkiter(&i, f); (k = key_next(&i)) != 0; ) {
+      if (key_extract(f, k, fp, 0)) {
+       fclose(fp);
+       goto fail_write;
+      }
+    }
+
+    if (fclose(fp))
+      goto fail_write;
+  }
+
+  /* --- Set up the other filenames --- */
+
+  dstr_putf(&n_older, "%s.older", f->name);
+  dstr_putf(&n_old, "%s.old", f->name);
+
+  /* --- Move the current backup on one --- *
+   *
+   * If the `older' file exists, then we're in need of attention.
+   */
+
+  {
+    struct stat st;
+    if (stat(n_older.buf, &st) == 0 || errno != ENOENT) {
+      errno = EEXIST;
+      rc = KWRITE_BROKEN;
+      goto fail_shift;
+    }
+    if (rename(n_old.buf, n_older.buf) && errno != ENOENT)
+      goto fail_shift;
+  }
+
+  /* --- Copy the current file to the backup --- */
+
+  {
+    int fd;
+    if ((fd = open(n_old.buf, O_WRONLY | O_CREAT | O_EXCL, 0600)) < 0)
+      goto fail_backup;
+    if (fdcopy(fileno(f->fp), fd)) {
+      close(fd);
+      goto fail_backup;
+    }
+    if (close(fd))
+      goto fail_backup;
+  }
+
+  /* --- Copy the newly created file to the current one --- *
+   *
+   * This is the dangerous bit.
+   */
+
+  {
+    int fd;
+    if ((fd = open(n_new.buf, O_RDONLY)) < 0)
+      goto fail_update;
+    if (fdcopy(fd, fileno(f->fp))) {
+      close(fd);
+      goto fail_update;
+    }
+    close(fd);
+    if (fsync(fileno(f->fp)))
+      goto fail_update;
+  }
+
+  /* --- Clean up --- *
+   *
+   * Remove the `new' file and the `older' backup.  Then we're done.
+   */
+
+  unlink(n_new.buf);
+  unlink(n_older.buf);
+  return (KWRITE_OK);
+
+  /* --- Failure while writing the new key file --- *
+   *
+   * I need to copy the backup back.  If that fails then I'm really stuffed.
+   * If not, then I might as well try to get the backups sorted back out
+   * again.
+   */
+
+fail_update:
+  {
+    int fd;
+    int e = errno;
+
+    if ((fd = open(n_old.buf, O_RDONLY)) < 0)
+      rc = KWRITE_BROKEN;
+    else if (fdcopy(fd, fileno(f->fp))) {
+      close(fd);
+      rc = KWRITE_BROKEN;
+    } else {
+      close(fd);
+      if (fsync(fileno(f->fp)))
+       rc = KWRITE_BROKEN;
+    }
+
+    errno = e;
+    if (rc == KWRITE_BROKEN)
+      goto fail_shift;
+  }
+  /* Now drop through */
+
+  /* --- Failure while writing the new backup --- *
+   *
+   * The new backup isn't any use.  Try to recover the old one.
+   */
+
+fail_backup:
+  {
+    int e = errno;
+    unlink(n_old.buf);
+    if (rename(n_older.buf, n_old.buf) && errno != ENOENT)
+      rc = KWRITE_BROKEN;
+    errno = e;
+  }
+  /* Now drop through */
+
+  /* --- Failure while demoting the current backup --- *
+   *
+   * Leave the completed output file there for the operator in case he wants
+   * to clean up.
+   */
+
+fail_shift:
+  dstr_destroy(&n_new);
+  dstr_destroy(&n_old);
+  dstr_destroy(&n_older);
+  return (rc);
+  
+/* --- Failure during write of new data --- *
+ *
+ * Clean up the new file and return.  These errors can never cause
+ * breakage.
+ */
+
+fail_write:
+  unlink(n_new.buf);
+  fail_open:
+  dstr_destroy(&n_new);
+  return (rc);
+}
+
+/* --- @key_lockfile@ --- *
+ *
+ * Arguments:  @key_file *f@ = pointer to file structure to initialize
+ *             @const char *file@ = pointer to the file name
+ *             @int how@ = opening options (@KOPEN_*@).
+ *
+ * Returns:    Zero if it worked, nonzero otherwise.
+ *
+ * Use:                Opens a keyfile and stores the information needed for
+ *             continued access in the structure.
+ *
+ *             If the file is opened with @KOPEN_WRITE@, it's created if
+ *             necessary with read and write permissions for owner only, and
+ *             locked for update while it's open.
+ *
+ *             This is a system-dependent routine, and only really intended
+ *             for the private use of @key_open@.
+ */
+
+int key_lockfile(key_file *f, const char *file, int how)
+{
+  int of, lf;
+  const char *ff;
+  int fd;
+
+  /* --- Lots of things depend on whether we're writing --- */
+
+  switch (how) {
+    case KOPEN_READ:
+      of = O_RDONLY;
+      lf = LOCK_NONEXCL;
+      ff = "r";
+      break;
+    case KOPEN_WRITE:
+      of = O_RDWR | O_CREAT;
+      lf = LOCK_EXCL;
+      ff = "r+";
+      break;
+    default:
+      errno = EINVAL;
+      return (-1);
+  }
+
+  /* --- Open and lock the file --- */
+
+  if ((fd = open(file, of, 0600)) < 0)
+    return (-1);
+  if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0 ||
+      lock_file(fd, lf) < 0 ||
+      (f->fp = fdopen(fd, ff)) == 0) {
+    close(fd);
+    return (-1);
+  }
+
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/