--- /dev/null
+/*
+ * keydb_stacked.c - backend that stacks other backends together
+ *
+ * Copyright 2016 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 <string.h>
+
+#include "decodekey.h"
+#include "hash.h"
+#include "keydb.h"
+#include "keyid.h"
+#include "keystructs.h"
+#include "log.h"
+#include "mem.h"
+#include "merge.h"
+#include "onak-conf.h"
+#include "openpgp.h"
+#include "parsekey.h"
+#include "sendsync.h"
+
+struct onak_stacked_dbctx {
+ struct ll *backends;
+ bool store_on_fallback;
+};
+
+/*
+ * The following functions only apply to the first backend.
+ */
+
+static bool stacked_starttrans(struct onak_dbctx *dbctx)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend =
+ (struct onak_dbctx *) privctx->backends->object;
+
+ return backend->starttrans(backend);
+}
+
+static void stacked_endtrans(struct onak_dbctx *dbctx)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend =
+ (struct onak_dbctx *) privctx->backends->object;
+
+ backend->starttrans(backend);
+}
+
+static int stacked_store_key(struct onak_dbctx *dbctx,
+ struct openpgp_publickey *publickey, bool intrans,
+ bool update)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend =
+ (struct onak_dbctx *) privctx->backends->object;
+
+ return backend->store_key(backend,
+ publickey, intrans, update);
+}
+
+static int stacked_delete_key(struct onak_dbctx *dbctx, uint64_t keyid,
+ bool intrans)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend =
+ (struct onak_dbctx *) privctx->backends->object;
+
+ return backend->delete_key(backend,
+ keyid, intrans);
+}
+
+static int stacked_update_keys(struct onak_dbctx *dbctx,
+ struct openpgp_publickey **keys, bool sendsync)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend =
+ (struct onak_dbctx *) privctx->backends->object;
+
+ return backend->update_keys(backend, keys, sendsync);
+}
+
+static int stacked_iterate_keys(struct onak_dbctx *dbctx,
+ void (*iterfunc)(void *ctx, struct openpgp_publickey *key),
+ void *ctx)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend =
+ (struct onak_dbctx *) privctx->backends->object;
+
+ return backend->iterate_keys(backend, iterfunc, ctx);
+}
+
+static void store_on_fallback(struct onak_stacked_dbctx *privctx,
+ struct openpgp_publickey *publickey, bool intrans)
+{
+ struct onak_dbctx *backend =
+ (struct onak_dbctx *) privctx->backends->object;
+ struct openpgp_publickey *curkey;
+
+ /*
+ * If we walked the stack at all, store the key in the first
+ * backend if configured to do so. It's not an update as we
+ * know it's not there or we wouldn't have fallen back.
+ */
+ for (curkey = publickey; curkey != NULL; curkey = curkey->next) {
+ backend->store_key(backend, curkey, intrans, false);
+ }
+}
+
+/*
+ * The functions below will walk along the backend stack until they
+ * reach the end or get a successful result.
+ */
+
+static int stacked_fetch_key_id(struct onak_dbctx *dbctx, uint64_t keyid,
+ struct openpgp_publickey **publickey, bool intrans)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend;
+ struct ll *cur;
+ int res = 0;
+
+ for (cur = privctx->backends; cur != NULL && res == 0;
+ cur = cur->next) {
+ backend = (struct onak_dbctx *) cur->object;
+ res = backend->fetch_key_id(backend, keyid, publickey,
+ intrans);
+ }
+
+ if (privctx->store_on_fallback && cur != privctx->backends) {
+ store_on_fallback(privctx, *publickey, intrans);
+ }
+
+ return res;
+}
+
+static int stacked_fetch_key_fp(struct onak_dbctx *dbctx,
+ struct openpgp_fingerprint *fingerprint,
+ struct openpgp_publickey **publickey, bool intrans)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend;
+ struct ll *cur;
+ int res = 0;
+
+ for (cur = privctx->backends; cur != NULL && res == 0;
+ cur = cur->next) {
+ backend = (struct onak_dbctx *) cur->object;
+ res = backend->fetch_key_fp(backend, fingerprint, publickey,
+ intrans);
+ }
+
+ if (privctx->store_on_fallback && cur != privctx->backends) {
+ store_on_fallback(privctx, *publickey, intrans);
+ }
+
+ return res;
+}
+
+static int stacked_fetch_key_text(struct onak_dbctx *dbctx,
+ const char *search,
+ struct openpgp_publickey **publickey)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend;
+ struct ll *cur;
+ int res = 0;
+
+ for (cur = privctx->backends; cur != NULL && res == 0;
+ cur = cur->next) {
+ backend = (struct onak_dbctx *) cur->object;
+ res = backend->fetch_key_text(backend, search, publickey);
+ }
+
+ if (privctx->store_on_fallback && cur != privctx->backends) {
+ store_on_fallback(privctx, *publickey, false);
+ }
+
+ return res;
+}
+
+static int stacked_fetch_key_skshash(struct onak_dbctx *dbctx,
+ const struct skshash *hash,
+ struct openpgp_publickey **publickey)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend;
+ struct ll *cur;
+ int res = 0;
+
+ for (cur = privctx->backends; cur != NULL && res == 0;
+ cur = cur->next) {
+ backend = (struct onak_dbctx *) cur->object;
+ res = backend->fetch_key_skshash(backend, hash, publickey);
+ }
+
+ if (privctx->store_on_fallback && cur != privctx->backends) {
+ store_on_fallback(privctx, *publickey, false);
+ }
+
+ return res;
+}
+
+static struct ll *stacked_getkeysigs(struct onak_dbctx *dbctx,
+ uint64_t keyid, bool *revoked)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend;
+ struct ll *cur;
+ struct ll *res = NULL;
+
+ for (cur = privctx->backends; cur != NULL && res == NULL;
+ cur = cur->next) {
+ backend = (struct onak_dbctx *) cur->object;
+ res = backend->getkeysigs(backend, keyid, revoked);
+ }
+
+ return res;
+}
+
+static struct ll *stacked_cached_getkeysigs(struct onak_dbctx *dbctx,
+ uint64_t keyid)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend;
+ struct ll *cur;
+ struct ll *res = NULL;
+
+ for (cur = privctx->backends; cur != NULL && res == NULL;
+ cur = cur->next) {
+ backend = (struct onak_dbctx *) cur->object;
+ res = backend->cached_getkeysigs(backend, keyid);
+ }
+
+ return res;
+}
+
+static char *stacked_keyid2uid(struct onak_dbctx *dbctx,
+ uint64_t keyid)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend;
+ struct ll *cur;
+ char *res = NULL;
+
+ for (cur = privctx->backends; cur != NULL && res == NULL;
+ cur = cur->next) {
+ backend = (struct onak_dbctx *) cur->object;
+ res = backend->keyid2uid(backend, keyid);
+ }
+
+ return res;
+}
+
+static uint64_t stacked_getfullkeyid(struct onak_dbctx *dbctx,
+ uint64_t keyid)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend;
+ struct ll *cur;
+ uint64_t res = 0;
+
+ for (cur = privctx->backends; cur != NULL && res == 0;
+ cur = cur->next) {
+ backend = (struct onak_dbctx *) cur->object;
+ res = backend->getfullkeyid(backend, keyid);
+ }
+
+ return res;
+}
+
+static void stacked_cleanupdb(struct onak_dbctx *dbctx)
+{
+ struct onak_stacked_dbctx *privctx =
+ (struct onak_stacked_dbctx *) dbctx->priv;
+ struct onak_dbctx *backend;
+ struct ll *cur;
+ int res = 0;
+
+ for (cur = privctx->backends; cur != NULL && res == 0;
+ cur = cur->next) {
+ backend = (struct onak_dbctx *) cur->object;
+ backend->cleanupdb(backend);
+ }
+
+ if (dbctx->priv != NULL) {
+ free(dbctx->priv);
+ dbctx->priv = NULL;
+ }
+
+ if (dbctx != NULL) {
+ free(dbctx);
+ }
+}
+
+struct onak_dbctx *keydb_stacked_init(struct onak_db_config *dbcfg,
+ bool readonly)
+{
+ struct onak_dbctx *dbctx;
+ struct onak_stacked_dbctx *privctx;
+ struct onak_dbctx *backend;
+ struct onak_db_config *backend_cfg;
+ char *backend_name, *saveptr = NULL;
+
+ if (dbcfg == NULL) {
+ logthing(LOGTHING_CRITICAL,
+ "No backend database configuration supplied.");
+ return NULL;
+ }
+
+ dbctx = malloc(sizeof(struct onak_dbctx));
+
+ if (dbctx == NULL) {
+ return NULL;
+ }
+
+ dbctx->config = dbcfg;
+ dbctx->priv = privctx = malloc(sizeof(struct onak_stacked_dbctx));
+ if (dbctx->priv == NULL) {
+ free(dbctx);
+ return (NULL);
+ }
+
+ /* TODO: Make configurable? */
+ privctx->store_on_fallback = true;
+ privctx->backends = NULL;
+
+ backend_name = strtok_r(dbcfg->location, ":", &saveptr);
+ while (backend_name != NULL) {
+ backend_cfg = find_db_backend_config(config.backends,
+ backend_name);
+ if (backend_cfg == NULL) {
+ logthing(LOGTHING_CRITICAL,
+ "Couldn't find configuration for %s backend",
+ backend_name);
+ stacked_cleanupdb(dbctx);
+ return NULL;
+ }
+ logthing(LOGTHING_INFO, "Loading stacked backend: %s",
+ backend_cfg->name);
+
+ backend = config.dbinit(backend_cfg, readonly);
+ privctx->backends = lladdend(privctx->backends, backend);
+
+ backend_name = strtok_r(NULL, ":", &saveptr);
+ }
+
+ if (privctx->backends != NULL) {
+ dbctx->cleanupdb = stacked_cleanupdb;
+ dbctx->starttrans = stacked_starttrans;
+ dbctx->endtrans = stacked_endtrans;
+ dbctx->fetch_key_id = stacked_fetch_key_id;
+ dbctx->fetch_key_fp = stacked_fetch_key_fp;
+ dbctx->fetch_key_text = stacked_fetch_key_text;
+ dbctx->fetch_key_skshash = stacked_fetch_key_skshash;
+ dbctx->store_key = stacked_store_key;
+ dbctx->update_keys = stacked_update_keys;
+ dbctx->delete_key = stacked_delete_key;
+ dbctx->getkeysigs = stacked_getkeysigs;
+ dbctx->cached_getkeysigs = stacked_cached_getkeysigs;
+ dbctx->keyid2uid = stacked_keyid2uid;
+ dbctx->getfullkeyid = stacked_getfullkeyid;
+ dbctx->iterate_keys = stacked_iterate_keys;
+ }
+
+ return dbctx;
+}