--- /dev/null
+/* -*-c-*-
+ *
+ * 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.
+ */
+
+/*----- 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];
+ char *p;
+
+ 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;
+ p = buf;
+ while (n) {
+ int nn = write(dest, p, n);
+ if (nn < 0)
+ return (-1);
+ p += nn;
+ n -= nn;
+ }
+ }
+ 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);
+ if (!f->fp)
+ return (KWRITE_FAIL);
+
+ /* --- 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);
+ dstr_destroy(&n_new);
+ dstr_destroy(&n_old);
+ dstr_destroy(&n_older);
+ 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
+ * @unsigned 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, unsigned how)
+{
+ int of, lf;
+ const char *ff;
+ int fd;
+
+ /* --- Handle the magic no-file option --- */
+
+ if (how & KOPEN_NOFILE) {
+ f->fp = 0;
+ return (0);
+ }
+
+ /* --- Lots of things depend on whether we're writing --- */
+
+ switch (how & KOPEN_MASK) {
+ 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 -------------------------------------------------*/