const char rcsid_kd_generic_c[] = "$Id: kd_generic.c,v 1.9 1996/03/12 05:40:00 marc Exp $";

#include <db.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "database.h"
#include "globals.h"
#include "llist.h"
#include "kd_types.h"
#include "kd_internal.h"

/* this file contains functions which are common to two different
   database operations, and functions which perform operations
   on the database itself. */

DB *keydb = NULL, *worddb = NULL, *timedb = NULL;

int kd_add_userid_to_wordlist(llist *wl,
			      unsigned char *userid, long userid_len)
{
   unsigned char *start;
   unsigned char *end;
   words_elem *tmp;
   int ret;

   end = userid;

   while (end < userid+userid_len) {
      /* find beginning of word */
      start = end;
      while ((start < userid+userid_len) && !isalnum(*start))
	 start++;

      /* find end of word */
      end = start;
      while ((end < userid+userid_len) && isalnum(*end))
	 end++;

      /* store it if it's > 1 char */

      if (end-start > 1) {
	 if ((tmp = (words_elem *) malloc(sizeof(words_elem))) == NULL)
	    fail();
	 tmp->ptr = start;
	 tmp->len = end-start;
	 if (!(ret = llist_add_sorted(wl, tmp, words_elem_order))) {
	    free(tmp);
	    fail();
	 }
	 if (ret == -1)
	    free(tmp);
      }
   }

   return(1);
}

int sigs_elem_marshall(void *e, void *c)
{
   sigs_elem *se = (sigs_elem *) e;
   xbuffer *xb = (xbuffer *) c;

   return(xbuffer_append(xb, se->sig.buf, se->sig.len));
}

int userids_elem_marshall(void *e, void *c)
{
   userids_elem *ue = (userids_elem *) e;
   xbuffer *xb = (xbuffer *) c;

   if (!xbuffer_append(xb, ue->uid.buf, ue->uid.len))
      fail();

   return(llist_iterate(&(ue->sigs), sigs_elem_marshall, c));
}

int kd_keys_elem_marshall(void *e, void *c)
{
   keys_elem *ke = (keys_elem *) e;
   xbuffer *xb = (xbuffer *) c;

   if (ke->disabled > 0)
      return(1);

   if (!xbuffer_append(xb, ke->pubkey.buf, ke->pubkey.len))
      fail();

   if (ke->disabled)
      if (!xbuffer_append_str(xb, "\260\001\040"))
	 fail();

   if (!xbuffer_append(xb, ke->revocation.buf, ke->revocation.len))
      fail();

   if (!xbuffer_append(xb, ke->primary->uid.buf, ke->primary->uid.len))
      fail();

   if (!llist_iterate(&(ke->primary->sigs), sigs_elem_marshall, c))
      fail();

   return(llist_iterate(&(ke->userids), userids_elem_marshall, c));
}

int kd_db_store_keyblock(llist *keys, error *err)
{
   DBT key, newdata;
   xbuffer newxb;
   keys_elem *ke = (keys_elem *) (*((void **) keys->xb.buf));

   /* ke points to first key, which is enough to derive the keyid
      for the database key */

   xbuffer_alloc(&newxb);

   if (!llist_iterate(keys, kd_keys_elem_marshall, (void *) &newxb)) {
      xbuffer_free(&newxb);
      err->fatal = 1;
      err->str = "internal error while marshalling keyblock";
      fail();
   }

   key.data = ke->modbits.buf+ke->modbits.len-KEYDB_KEYID_BYTES;
   key.size = KEYDB_KEYID_BYTES;

   newdata.data = (void *) newxb.buf;
   newdata.size = (size_t) newxb.len;

   if ((*(keydb->put))(keydb, &key, &newdata, 0) < 0) {
      xbuffer_free(&newxb);
      err->fatal = 1;
      sprintf(err->buf, "error %s keydb, errno = %d", "writing to", errno);
      fail();
   }

   xbuffer_free(&newxb);

   return(1);
}

/* log utility functions */

void kd_log_start(char *fct, unsigned char *userid, long len, int flags)
{
   char buf[1024];

   if (userid)
      sprintf(buf, "userid=\"%.*s\"%s, flags=%x",
	      (int) ((len<=900)?len:900), userid, (len<=900)?"":" (truncated)",
	      flags);
   else
      sprintf(buf, "flags=%x", flags);

   log_info(fct, buf);
}

void kd_log_finish(char *fct, int success)
{
   if (success)
      log_info(fct, "completed successfully");
   else
      log_info(fct, "completed with error");
}

/* create/open/close/sync */

int kd_open_1(char *dbdir, int create, error *err)
{
   HASHINFO keyinfo, wordinfo;
   BTREEINFO timeinfo;
   int flags;

   if (chdir(dbdir) < 0) {
      err->fatal = 1;
      sprintf(err->buf, "Error changing to db directory (errno = %d)",
	      errno);
      fail();
   }

   flags = O_RDWR|(create?(O_CREAT|O_TRUNC):0);

   keyinfo.bsize = 2048;
   keyinfo.cachesize = 65536;
   keyinfo.ffactor = 8;
   keyinfo.hash = NULL;
   keyinfo.lorder = 0;
   keyinfo.nelem = 1;

   if (!(keydb = dbopen("keydb", flags, 0644, DB_HASH, &keyinfo))) {
      err->fatal = 1;
      sprintf(err->buf, "Error opening keydb (errno = %d)", errno);
      fail();
   }

   if (fcntl((*(keydb->fd))(keydb), F_SETFD, 1) < 0) {
      err->fatal = 1;
      sprintf(err->buf, "failed making keydb close-on-exec: %d", errno);
      fail();
   }

   wordinfo.bsize = 512;
   wordinfo.cachesize = 65536;
   wordinfo.ffactor = 8;
   wordinfo.hash = NULL;
   wordinfo.lorder = 0;
   wordinfo.nelem = 1;

   if (!(worddb = dbopen("worddb", flags, 0644, DB_HASH, &wordinfo))) {
      (*(keydb->close))(keydb);

      err->fatal = 1;
      sprintf(err->buf, "Error opening worddb (errno = %d)", errno);
      fail();
   }

   if (fcntl((*(worddb->fd))(worddb), F_SETFD, 1) < 0) {
      err->fatal = 1;
      sprintf(err->buf, "failed making keydb close-on-exec: %d", errno);
      fail();
   }

   timeinfo.flags = 0;
   timeinfo.cachesize = 16384;
   timeinfo.psize = 512;
   timeinfo.lorder = 0;
   timeinfo.minkeypage = 16;
   timeinfo.compare = NULL;
   timeinfo.prefix = NULL;

   if (!(timedb = dbopen("timedb", flags, 0644, DB_BTREE, &timeinfo))) {
      (*(worddb->close))(worddb);
      (*(keydb->close))(keydb);

      err->fatal = 1;
      sprintf(err->buf, "Error opening timedb (errno = %d)", errno);
      fail();
   }

   if (fcntl((*(timedb->fd))(timedb), F_SETFD, 1) < 0) {
      err->fatal = 1;
      sprintf(err->buf, "failed making keydb close-on-exec: %d", errno);
      fail();
   }

   return(1);
}

/* this not only copies files, but leaves holes if there's no data */

static const char zeros[1024];

int copy_file(const char *src, const char *dst)
{
   int fsrc, fdst;
   unsigned char buf[1024];
   int cnt, total;

   total = 0;

   if ((fsrc = open(src, O_RDONLY)) < 0)
      return(-1);

   if ((fdst = open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0) {
      close(fsrc);
      return(-1);
   }

   while (1) {
      cnt = read(fsrc, (void *) buf, sizeof(buf));

      if (cnt < 0) {
	 close(fsrc);
	 close(fdst);
	 return(-1);
      }

      if (cnt == 0)
	 break;

      total += cnt;

      if (memcmp(buf, zeros, cnt) == 0) {
	 if (lseek(fdst, cnt, SEEK_CUR) < 0) {
	    close(fsrc);
	    close(fdst);
	    return(-1);
	 }
      } else {
	 int wptr = 0;

	 while (cnt > 0) {
	    if ((wptr = write(fdst, (void *) (buf+wptr), cnt)) < 0) {
	       close(fsrc);
	       close(fdst);
	       return(-1);
	    }

	    cnt -= wptr;
	 }
      }
   }

   close(fsrc);
   close(fdst);

   return(total);
}

int kd_backup_1()
{
   char buf[1024];
   struct stat st;
   
   if ((mkdir("backup", 0755) < 0) && (errno != EEXIST)) {
      kd_sync();
      sprintf(buf, "failed creating backup/ dir: errno = %d", errno);
      log_error("kd_backup_1", buf);
      return(0);
   }

   if ((unlink("backup/keydb") < 0) && (errno != ENOENT)) {
      kd_sync();
      sprintf(buf, "failed removing old backup/keydb: errno = %d", errno);
      log_error("kd_backup_1", buf);
      return(0);
   }
		
   if ((unlink("backup/worddb") < 0) && (errno != ENOENT)) {
      kd_sync();
      sprintf(buf, "failed removing old backup/worddb: errno = %d", errno);
      log_error("kd_backup_1", buf);
      return(0);
   }
		
   if ((unlink("backup/timedb") < 0) && (errno != ENOENT)) {
      kd_sync();
      sprintf(buf, "failed removing old backup/timedb: errno = %d", errno);
      log_error("kd_backup_1", buf);
      return(0);
   }
		
   kd_sync();

   if (copy_file("keydb", "backup/keydb") < 0) {
      kd_sync();
      sprintf(buf, "failed copying keydb to backup/keydb: errno = %d", errno);
      log_error("kd_backup_1", buf);
      return(0);
   }
      
   if (copy_file("worddb", "backup/worddb") < 0) {
      kd_sync();
      sprintf(buf, "failed copying worddb to backup/worddb: errno = %d",
	      errno);
      log_error("kd_backup_1", buf);
      return(0);
   }
      
   if (copy_file("timedb", "backup/timedb") < 0) {
      kd_sync();
      sprintf(buf, "failed copying timedb to backup/timedb: errno = %d",
	      errno);
      log_error("kd_backup_1", buf);
      return(0);
   }

   return(1);
}

void kd_sync_1()
{
   (*(timedb->sync))(timedb, 0);
   (*(worddb->sync))(worddb, 0);
   (*(keydb->sync))(keydb, 0);
}

void kd_close_1()
{
   if (timedb)
      (*(timedb->close))(timedb);
   if (worddb)
      (*(worddb->close))(worddb);
   if (keydb)
      (*(keydb->close))(keydb);
}

int kd_create(char *dbdir, char **ret)
{
   error err;

   err.str = err.buf;

   kd_log_start("kd_create", NULL, 0, 0);

   if (kd_open_1(dbdir, 1, &err)) {
      kd_log_finish("kd_create", 1);

      return(1);
   } else if (!err.fatal) {
      if (!(*ret = my_strdup(err.str))) {
	 err.fatal = 1;
	 err.str = "Failed allocating space for error string";
	 fail();

	 /* fall through to fatal error handler */
      } else {
	 kd_log_finish("kd_create", 0);

	 return(0);
      }
   }

   /* fatal errors */

   if (err.fatal) {
      log_fatal("kd_open", err.str);
      /* never returns */
   }

   /* keep the compiler quiet */

   return(0);
}

int kd_open(char *dbdir, char **ret)
{
   error err;

   err.str = err.buf;

   if (kd_open_1(dbdir, 0, &err)) {
      kd_log_finish("kd_open", 1);

      return(1);
   } else if (!err.fatal) {
      if (!(*ret = my_strdup(err.str))) {
	 err.fatal = 1;
	 err.str = "Failed allocating space for error string";
	 fail();

	 /* fall through to fatal error handler */
      } else {
	 kd_log_finish("kd_open", 0);

	 return(0);
      }
   }

   /* fatal errors */

   if (err.fatal) {
      log_fatal("kd_open", err.str);
      /* never returns */
   }

   /* keep the compiler quiet */

   return(0);
}

int kd_backup()
{
   int ret;

   kd_log_start("kd_backup", NULL, 0, 0);

   ret = kd_backup_1();

   kd_log_finish("kd_backup", ret);

   return(ret);
}

int kd_sync()
{
   kd_sync_1();

   kd_log_finish("kd_sync", 1);

   return(1);
}

int kd_close()
{
   kd_close_1();

   kd_log_finish("kd_close", 1);

   return(1);
}
