From 54288021d24e0b40119d2ffae5a24ecd720d3aca Mon Sep 17 00:00:00 2001
From: Jonathan McDowell <noodles@earth.li>
Date: Mon, 30 Sep 2013 17:31:03 +0100
Subject: [PATCH] Add HKP database backend

A potential use case of onak is as a proxy server. Add an HKP backend
that uses libcurl to make requests to a remote keyserver to fetch, search
or store keys. The "db_dir" configuration parameter becomes the base
host name for the remote keyserver e.g.:

db_backend hkp
db_dir the.earth.li

In the future the addition of the ability to stack database backends
should allow this to be used to turn onak into a caching keyserver.
---
 Makefile.in    |   4 +
 configure.ac   |   2 +
 debian/control |   2 +-
 keydb_hkp.c    | 299 +++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 306 insertions(+), 1 deletion(-)
 create mode 100644 keydb_hkp.c

diff --git a/Makefile.in b/Makefile.in
index ead45eb..ec8b614 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -11,6 +11,7 @@ DBTYPE = @DBTYPE@
 #
 LIBS = @LIBS@ @NETTLE_LIBS@
 DB4LIBS = @DB4LIBS@
+CURLLIBS = @LIBCURL@
 #MAKEDEPEND = makedepend -f- -- 
 MAKEDEPEND = $(CC) -MM
 prefix ?= @prefix@
@@ -84,6 +85,9 @@ keydctl: keydctl.o onak-conf.o ll.o log.o
 libkeydb_db4.so: keydb_db4.o $(CORE_OBJS)
 	$(CC) -shared $(DB4LIBS) -o libkeydb_db4.so keydb_db4.o $(CORE_OBJS)
 
+libkeydb_hkp.so: keydb_hkp.o $(CORE_OBJS)
+	$(CC) -shared $(CURLLIBS) -o libkeydb_hkp.so keydb_hkp.o $(CORE_OBJS)
+
 libkeydb_pg.so: keydb_pg.o $(CORE_OBJS)
 	$(CC) -shared $(PQLIBS) -o libkeydb_pg.so keydb_pg.o $(CORE_OBJS)
 
diff --git a/configure.ac b/configure.ac
index 80e7d73..55def5c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -15,6 +15,8 @@ AX_LIB_NETTLE(auto)
 dnl We should always have these backends available.
 backends="file fs keyd"
 
+LIBCURL_CHECK_CONFIG(,,[have_libcurl="yes" backends="$backends hkp"],have_libcurl="no")
+
 AC_CHECK_LIB(pq, PQsetdbLogin,[have_libpq="yes" backends="$backends pg"],have_libpq="no")
 
 AC_CHECK_HEADER(db.h, have_db_h="yes", have_db_h="no")
diff --git a/debian/control b/debian/control
index f8bb920..47988e5 100644
--- a/debian/control
+++ b/debian/control
@@ -3,7 +3,7 @@ Section: net
 Priority: optional
 Maintainer: Jonathan McDowell <noodles@earth.li>
 Uploaders: Ross Burton <ross@debian.org>
-Build-Depends: debhelper (>= 7), cdbs, libdb-dev, nettle-dev
+Build-Depends: debhelper (>= 7), cdbs, libdb-dev, nettle-dev, libcurl4-gnutls-dev | libcurl-dev
 Standards-Version: 3.9.2.0
 Homepage: http://www.earth.li/projectpurple/progs/onak.html
 Vcs-Bzr: http://www.earth.li/~noodles/bzr/onak/mainline
diff --git a/keydb_hkp.c b/keydb_hkp.c
new file mode 100644
index 0000000..f1dfe44
--- /dev/null
+++ b/keydb_hkp.c
@@ -0,0 +1,299 @@
+/*
+ * keydb_hkp.c - Routines to store and fetch keys from another keyserver.
+ *
+ * Copyright 2013 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <curl/curl.h>
+
+#include "armor.h"
+#include "charfuncs.h"
+#include "keydb.h"
+#include "keystructs.h"
+#include "log.h"
+#include "mem.h"
+#include "onak-conf.h"
+#include "parsekey.h"
+
+static CURL *curl = NULL;
+
+/**
+ *	initdb - Initialize the key database.
+ *
+ *	We initialize CURL here.
+ */
+static void hkp_initdb(bool readonly)
+{
+	curl_global_init(CURL_GLOBAL_DEFAULT);
+	curl = curl_easy_init();
+}
+
+/**
+ *	cleanupdb - De-initialize the key database.
+ *
+ *	We cleanup CURL here.
+ */
+static void hkp_cleanupdb(void)
+{
+	if (curl) {
+		curl_easy_cleanup(curl);
+		curl = NULL;
+	}
+	curl_global_cleanup();
+}
+
+/**
+ *	Receive data from a CURL request and process it into a buffer context.
+ */
+static size_t hkp_curl_recv_data(void *buffer, size_t size, size_t nmemb,
+		void *ctx)
+{
+	buffer_putchar(ctx, nmemb * size, buffer);
+
+	return (nmemb * size);
+}
+
+/**
+ *	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.
+ */
+static int hkp_fetch_key(uint64_t keyid, struct openpgp_publickey **publickey,
+		bool intrans)
+{
+	struct openpgp_packet_list *packets = NULL;
+	char keyurl[1024];
+	CURLcode res;
+	struct buffer_ctx buf;
+
+	buf.offset = 0;
+	buf.size = 8192;
+	buf.buffer = malloc(8192);
+
+	snprintf(keyurl, sizeof(keyurl),
+			"http://%s:11371/pks/lookup?op=get&search=0x%08" PRIX64,
+			config.db_dir, keyid);
+
+	curl_easy_setopt(curl, CURLOPT_URL, keyurl);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
+			hkp_curl_recv_data);
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
+	res = curl_easy_perform(curl);
+
+	if (res == 0) {
+		buf.offset = 0;
+		dearmor_openpgp_stream(buffer_fetchchar, &buf, &packets);
+		parse_keys(packets, publickey);
+		free_packet_list(packets);
+		packets = NULL;
+	} else {
+		logthing(LOGTHING_ERROR, "Couldn't find key: %s (%d)",
+			curl_easy_strerror(res), res);
+	}
+
+	free(buf.buffer);
+	buf.offset = buf.size = 0;
+	buf.buffer = NULL;
+
+	return (res == 0) ? 1 : 0;
+}
+
+/**
+ *	fetch_key_text - Tries 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.
+ *
+ *	TODO: Write for flat file access. Some sort of grep?
+ */
+static int hkp_fetch_key_text(const char *search,
+		struct openpgp_publickey **publickey)
+{
+	struct openpgp_packet_list *packets = NULL;
+	char keyurl[1024];
+	CURLcode res;
+	struct buffer_ctx buf;
+	int count = 0;
+
+	buf.offset = 0;
+	buf.size = 8192;
+	buf.buffer = malloc(8192);
+
+	snprintf(keyurl, sizeof(keyurl),
+			"http://%s:11371/pks/lookup?op=get&search=%s",
+			config.db_dir, search);
+
+	curl_easy_setopt(curl, CURLOPT_URL, keyurl);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
+			hkp_curl_recv_data);
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
+	res = curl_easy_perform(curl);
+
+	if (res == 0) {
+		buf.offset = 0;
+		dearmor_openpgp_stream(buffer_fetchchar, &buf, &packets);
+		count = parse_keys(packets, publickey);
+		free_packet_list(packets);
+		packets = NULL;
+	} else {
+		logthing(LOGTHING_ERROR, "Couldn't find key: %s (%d)",
+			curl_easy_strerror(res), res);
+	}
+
+	free(buf.buffer);
+	buf.offset = buf.size = 0;
+	buf.buffer = NULL;
+
+	return count;
+}
+
+/**
+ *	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.
+ *
+ */
+static int hkp_store_key(struct openpgp_publickey *publickey, bool intrans,
+		bool update)
+{
+	struct openpgp_packet_list *packets = NULL;
+	struct openpgp_packet_list *list_end = NULL;
+	char keyurl[1024];
+	CURLcode res;
+	struct buffer_ctx buf;
+	char *addform;
+
+	buf.offset = 0;
+	buf.size = 8192;
+	buf.buffer = malloc(8192);
+	buf.offset = snprintf(buf.buffer, buf.size, "keytextz");
+
+	flatten_publickey(publickey, &packets, &list_end);
+	armor_openpgp_stream(buffer_putchar, &buf, packets);
+	addform = curl_easy_escape(curl, buf.buffer, buf.offset);
+	addform[7] = '=';
+
+	snprintf(keyurl, sizeof(keyurl),
+			"http://%s:11371/pks/add",
+			config.db_dir);
+
+	curl_easy_setopt(curl, CURLOPT_URL, keyurl);
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, addform);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
+			hkp_curl_recv_data);
+	buf.offset = 0;
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);
+	res = curl_easy_perform(curl);
+
+	if (res != 0) {
+		logthing(LOGTHING_ERROR, "Couldn't send key: %s (%d)",
+			curl_easy_strerror(res), res);
+	}
+
+	curl_free(addform);
+
+	/* TODO: buf has any response text we might want to parse. */
+	free(buf.buffer);
+	buf.offset = buf.size = 0;
+	buf.buffer = NULL;
+
+	return (res == 0) ? 1 : 0;
+}
+
+/**
+ *	delete_key - Given a keyid delete the key from storage.
+ *	@keyid: The keyid to delete.
+ *	@intrans: If we're already in a transaction.
+ *
+ *	No op for HKP.
+ */
+static int hkp_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
+ *
+ *	Not applicable for HKP backend.
+ */
+static int hkp_iterate_keys(void (*iterfunc)(void *ctx,
+		struct openpgp_publickey *key),	void *ctx)
+{
+	return 0;
+}
+
+/**
+ *	starttrans - Start a transaction.
+ *
+ *	This is just a no-op for HKP access.
+ */
+static bool hkp_starttrans(void)
+{
+	return true;
+}
+
+/**
+ *	endtrans - End a transaction.
+ *
+ *	This is just a no-op for HKP access.
+ */
+static void hkp_endtrans(void)
+{
+	return;
+}
+
+/*
+ * Include the basic keydb routines.
+ */
+#define NEED_KEYID2UID 1
+#define NEED_GETKEYSIGS 1
+#define NEED_GETFULLKEYID 1
+#define NEED_UPDATEKEYS 1
+#include "keydb.c"
+
+struct dbfuncs keydb_hkp_funcs = {
+	.initdb			= hkp_initdb,
+	.cleanupdb		= hkp_cleanupdb,
+	.starttrans		= hkp_starttrans,
+	.endtrans		= hkp_endtrans,
+	.fetch_key		= hkp_fetch_key,
+	.fetch_key_text		= hkp_fetch_key_text,
+	.store_key		= hkp_store_key,
+	.update_keys		= generic_update_keys,
+	.delete_key		= hkp_delete_key,
+	.getkeysigs		= generic_getkeysigs,
+	.cached_getkeysigs	= generic_cached_getkeysigs,
+	.keyid2uid		= generic_keyid2uid,
+	.getfullkeyid		= generic_getfullkeyid,
+	.iterate_keys		= hkp_iterate_keys,
+};
-- 
2.39.5