/*
 * keydb_db2.c - Routines to store and fetch keys in a DB2 file (a la pksd)
 *
 * Copyright 2002-2004 Jonathan McDowell <noodles@earth.li>
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; version 2 of the License.
 *
 * This program 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 General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

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

#include "charfuncs.h"
#include "keydb.h"
#include "keyid.h"
#include "keyindex.h"
#include "keystructs.h"
#include "log.h"
#include "mem.h"
#include "onak-conf.h"
#include "parsekey.h"

#define KEYDB_KEYID_BYTES 4

/**
 *	db2_numdb - The number of database files we have.
 */
static int db2_numdb = 16;

/**
 *	db2_keydbfiles - An array of DB structs for our key database files.
 */
static DB **db2_keydbfiles = NULL;

/**
 *	db2_env - Database environment variable.
 */
static DB_ENV db2_env;

DB *keydb(DBT *key)
{
	/*
	 * keyid's are 8 bytes, msb first.  so start from the end.  use 16
	 * bits, since that's enough to divide by any small number of db files.
	 */
	unsigned char *keydata = (unsigned char *) key->data;
	unsigned long keyidnum;

	keyidnum = (keydata[KEYDB_KEYID_BYTES-2]<<8)|keydata[KEYDB_KEYID_BYTES-1];
	return(db2_keydbfiles[keyidnum % db2_numdb]);
}

/**
 *	initdb - Initialize the key database.
 *
 *	This function should be called before any of the other functions in
 *	this file are called in order to allow the DB to be initialized ready
 *	for access.
 */
void initdb(bool readonly)
{
	DB_INFO keydbinfo;
	int i;
	int ret;
	char keydbname[20];
	char buf[1024];
	FILE *numdb = NULL;

	snprintf(buf, sizeof(buf) - 1, "%s/num_keydb", config.db_dir);
	numdb = fopen(buf, "r");
	if (numdb != NULL) {
		if (fgets(buf, sizeof(buf), numdb) != NULL) {
			db2_numdb = atoi(buf);
		}
		fclose(numdb);
	} else {
		logthing(LOGTHING_ERROR, "Couldn't open num_keydb: %s",
				strerror(errno));
	}

	memset(&db2_env, 0, sizeof(db2_env));

	/*
	 * Tunable param. Just using what pksd does for the moment. Bigger uses
	 * more memory but improves performance. Bigger than physical memory
	 * makes no sense.
	 */
	db2_env.mp_size = 20 * 1024 * 1024;

	ret = db_appinit(config.db_dir, NULL,
			&db2_env, DB_INIT_MPOOL|DB_INIT_LOCK);
	if (!ret) {
		db2_keydbfiles = (DB **) malloc(sizeof (DB *) * db2_numdb);
		memset(&keydbinfo, 0, sizeof(keydbinfo));
		keydbinfo.db_pagesize = 8192;
		for (i = 0; i < db2_numdb; i++) {
			db2_keydbfiles[i] = NULL;
			snprintf(keydbname, 19, "keydb%03d", i);
			ret = db_open(keydbname, DB_HASH, DB_RDONLY, 0644,
					&db2_env, &keydbinfo,
					&db2_keydbfiles[i]);
			if (ret) {
				logthing(LOGTHING_CRITICAL,
					"Error opening db file %d (errno %d)",
					i, ret);
				exit(1);
			}
		}
	} else {
		logthing(LOGTHING_CRITICAL, "Error initializing db (%d).",
				ret);
		exit(1);
	}
}

/**
 *	cleanupdb - De-initialize the key database.
 *
 *	This function should be called upon program exit to allow the DB to
 *	cleanup after itself.
 */
void cleanupdb(void)
{
	int i;

	for (i = 0; i < db2_numdb; i++) {
		if (db2_keydbfiles[i] != NULL) {
			(*(db2_keydbfiles[i]->close))(db2_keydbfiles[i], 0);
			db2_keydbfiles[i] = NULL;
		}
	}

	db_appexit(&db2_env);
}

/**
 *	starttrans - Start a transaction.
 *
 *	Start a transaction. Intended to be used if we're about to perform many
 *	operations on the database to help speed it all up, or if we want
 *	something to only succeed if all relevant operations are successful.
 */
bool starttrans(void)
{
	return true;
}

/**
 *	endtrans - End a transaction.
 *
 *	Ends a transaction.
 */
void endtrans(void)
{
	return;
}

/**
 *	fetch_key - Given a keyid fetch the key from storage.
 *	@keyid: The keyid to fetch.
 *	@publickey: A pointer to a structure to return the key in.
 *	@intrans: If we're already in a transaction.
 *
 *	We use the hex representation of the keyid as the filename to fetch the
 *	key from. The key is stored in the file as a binary OpenPGP stream of
 *	packets, so we can just use read_openpgp_stream() to read the packets
 *	in and then parse_keys() to parse the packets into a publickey
 *	structure.
 */
int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey,
		bool intrans)
{
	struct openpgp_packet_list *packets = NULL;
	int ret;
	DBT key, data;
	char id[KEYDB_KEYID_BYTES];
	struct buffer_ctx fetchbuf;

	memset(&key, 0, sizeof(key));
	memset(&data, 0, sizeof(data));

	id[0] = (keyid >> 24) & 0xFF;
	id[1] = (keyid >> 16) & 0xFF;
	id[2] = (keyid >> 8) & 0xFF;
	id[3] = keyid & 0xFF;

	key.data = id;
	key.size = KEYDB_KEYID_BYTES;

	ret = (*(keydb(&key)->get))(keydb(&key), NULL, &key, &data, 0);
	if (ret == 0) {
		fetchbuf.buffer = data.data;
		fetchbuf.offset = 0;
		fetchbuf.size = data.size;
		read_openpgp_stream(buffer_fetchchar, &fetchbuf, &packets, 0);
		parse_keys(packets, publickey);
		free_packet_list(packets);
		packets = NULL;
	}

	return (!ret);
}

/**
 *	fetch_key_text - Trys to find the keys that contain the supplied text.
 *	@search: The text to search for.
 *	@publickey: A pointer to a structure to return the key in.
 *
 *	This function searches for the supplied text and returns the keys that
 *	contain it.
 */
int fetch_key_text(const char *search, struct openpgp_publickey **publickey)
{
	return 0;
}

/**
 *	store_key - Takes a key and stores it.
 *	@publickey: A pointer to the public key to store.
 *	@intrans: If we're already in a transaction.
 *	@update: If true the key exists and should be updated.
 *
 *	Again we just use the hex representation of the keyid as the filename
 *	to store the key to. We flatten the public key to a list of OpenPGP
 *	packets and then use write_openpgp_stream() to write the stream out to
 *	the file.
 */
int store_key(struct openpgp_publickey *publickey, bool intrans, bool update)
{
	return 0;
}

/**
 *	delete_key - Given a keyid delete the key from storage.
 *	@keyid: The keyid to delete.
 *	@intrans: If we're already in a transaction.
 *
 *	This function deletes a public key from whatever storage mechanism we
 *	are using. Returns 0 if the key existed.
 */
int delete_key(uint64_t keyid, bool intrans)
{
	return (1);
}

/**
 *	iterate_keys - call a function once for each key in the db.
 *	@iterfunc: The function to call.
 *	@ctx: A context pointer
 *
 *	Calls iterfunc once for each key in the database. ctx is passed
 *	unaltered to iterfunc. This function is intended to aid database dumps
 *	and statistic calculations.
 *
 *	Returns the number of keys we iterated over.
 */
int iterate_keys(void (*iterfunc)(void *ctx, struct openpgp_publickey *key),
		void *ctx)
{
	return 0;
}

/*
 * Include the basic keydb routines.
 */
#define NEED_KEYID2UID 1
#define NEED_GETKEYSIGS 1
#define NEED_GETFULLKEYID 1
#define NEED_UPDATEKEYS 1
#include "keydb.c"
