From e517c4f6effa3187dc46397e19aeada9574f7286 Mon Sep 17 00:00:00 2001
From: Jonathan McDowell <noodles@earth.li>
Date: Mon, 30 Sep 2013 15:18:31 +0100
Subject: [PATCH] Add initial wotsap file generation tool

Wotsap (http://www.lysator.liu.se/~jc/wotsap/) is a web of trust
statistics and pathfinding tool. It takes a set of preformatted key
data covering the primary UID and signatures on each key.

This commit adds a tool which will generate the file data required for
wotsap. These files still need ar/bzip2 run against them in order to
be fed into wotsap, but are generated from the live keyring data.
---
 .gitignore  |   1 +
 Makefile.in |  11 ++-
 wotsap.c    | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 214 insertions(+), 3 deletions(-)
 create mode 100644 wotsap.c

diff --git a/.gitignore b/.gitignore
index d7e1c66..6a7382c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@ sixdegrees
 splitkeys
 stripkey
 testparse
+wotsap
 tags
 .depend
 autom4te.cache
diff --git a/Makefile.in b/Makefile.in
index b5b8a55..ead45eb 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -16,7 +16,8 @@ MAKEDEPEND = $(CC) -MM
 prefix ?= @prefix@
 exec_prefix ?= @exec_prefix@
 
-PROGS = add lookup hashquery gpgwww onak splitkeys onak-mail.pl stripkey
+PROGS = add lookup hashquery gpgwww onak splitkeys onak-mail.pl stripkey \
+	wotsap
 CORE_OBJS = armor.o charfuncs.o decodekey.o getcgi.o hash.o marshal.o \
 	keyid.o keyindex.o ll.o mem.o onak-conf.o parsekey.o sigcheck.o \
 	log.o photoid.o wordlist.o cleanup.o merge.o sendsync.o keyarray.o
@@ -49,7 +50,7 @@ endif
 OBJS = stats.o cleankey.o $(CORE_OBJS) $(KEYDB_OBJ)
 
 all: .depend $(PROGS) testparse maxpath sixdegrees splitkeys onak.conf \
-	$(BACKENDS)
+	wotsap $(BACKENDS)
 
 test: onak $(BACKENDS)
 	@./runtests
@@ -105,6 +106,10 @@ sixdegrees: sixdegrees.o $(OBJS)
 	$(CC) $(LDFLAGS) -o sixdegrees sixdegrees.o $(OBJS) $(LIBS) \
 		$(PROGS_LDFLAGS_EXTRA)
 
+wotsap: wotsap.o $(OBJS)
+	$(CC) $(LDFLAGS) -o wotsap wotsap.o $(OBJS) $(LIBS) \
+		$(PROGS_LDFLAGS_EXTRA)
+
 stripkey: stripkey.o $(OBJS)
 	$(CC) $(LDFLAGS) -o stripkey stripkey.o $(OBJS) $(LIBS) \
 		$(PROGS_LDFLAGS_EXTRA)
@@ -152,7 +157,7 @@ clean:
 	$(RM) $(PROGS) $(OBJS) Makefile.bak testparse maxpath *.core core \
 		gpgwww.o add.o lookup.o main.o maxpath.o onak.o sixdegrees \
 		sixdegrees.o splitkeys.o stripkey.o onak.conf keyd.o \
-		keydctl.o hashquery.o version.h \
+		keydctl.o hashquery.o wotsap.o version.h \
 		TAGS cscope.out cscope.files \
 		$(foreach be,@BACKENDS@,keydb_$(be).o) *.so
 ifeq (x@KEYD@, xyes)
diff --git a/wotsap.c b/wotsap.c
new file mode 100644
index 0000000..339b949
--- /dev/null
+++ b/wotsap.c
@@ -0,0 +1,205 @@
+/*
+ * wotsap.c - Output a set of wotsap files from an onak keyring
+ *
+ * See:
+ *
+ * http://www.lysator.liu.se/~jc/wotsap/wotfileformat.txt
+ *
+ * for more details of the format.
+ *
+ * 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 <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include "hash.h"
+#include "log.h"
+#include "onak-conf.h"
+#include "stats.h"
+#include "version.h"
+
+static struct ll *sortkeyll(struct ll *keys)
+{
+	struct ll *newll, *tmp, **curobj;
+	struct stats_key *curkey, *toadd;
+
+	newll = NULL;
+	while (keys) {
+		toadd = (struct stats_key *) keys->object;
+		curobj = &newll;
+		while (*curobj) {
+			curkey = (struct stats_key *) (*curobj)->object;
+			if (curkey->keyid >= toadd->keyid) {
+				break;
+			}
+			curobj = &((*curobj)->next);
+		}
+
+		tmp = keys->next;
+		if (*curobj == NULL || curkey->keyid != toadd->keyid) {
+			keys->next = *curobj;
+			*curobj = keys;
+		}
+		keys = tmp;
+	}
+	return newll;
+}
+
+static void output_key(FILE *names, FILE *keys, uint64_t keyid)
+{
+	fprintf(names, "%s\n", config.dbbackend->keyid2uid(keyid));
+	fprintf(keys, "%c%c%c%c", (int) (keyid >> 24) & 0xFF,
+			(int) (keyid >> 16) & 0xFF,
+			(int) (keyid >>  8) & 0xFF,
+			(int) (keyid      ) & 0xFF);
+}
+
+static void wotsap(uint64_t keyid, char *dir)
+{
+	struct ll *pending, *sigll, *sigsave;
+	uint32_t curidx = 0;
+	struct stats_key *curkey, *addkey;
+	char *uid;
+	FILE *names, *keys, *sigs, *file;
+	char *tmppath;
+	uint32_t sigcount, sigentry;
+
+	/* Length of dir + "/" + "signatures" + NUL */
+	tmppath = malloc(strlen(dir) + 12);
+
+	sprintf(tmppath, "%s/WOTVERSION", dir);
+	file = fopen(tmppath, "w");
+	if (file == NULL) {
+		fprintf(stderr, "Couldn't open %s\n", tmppath);
+		return;
+	}
+	fprintf(file, "0.2\n");
+	fclose(file);
+
+	sprintf(tmppath, "%s/README", dir);
+	file = fopen(tmppath, "w");
+	if (file == NULL) {
+		fprintf(stderr, "Couldn't open %s\n", tmppath);
+		return;
+	}
+	fprintf(file, "This is a Web of Trust archive.\n");
+	fprintf(file, "The file format is documented at:\n");
+	fprintf(file, "  http://www.lysator.liu.se/~jc/wotsap/wotfileformat.txt\n\n");
+	fprintf(file, "This file was generated by onak " ONAK_VERSION " \n");
+	fclose(file);
+
+	sprintf(tmppath, "%s/names", dir);
+	names = fopen(tmppath, "w");
+	if (names == NULL) {
+		fprintf(stderr, "Couldn't open %s\n", tmppath);
+		return;
+	}
+	sprintf(tmppath, "%s/keys", dir);
+	keys = fopen(tmppath, "wb");
+	if (keys == NULL) {
+		fprintf(stderr, "Couldn't open %s\n", tmppath);
+		return;
+	}
+	sprintf(tmppath, "%s/signatures", dir);
+	sigs = fopen(tmppath, "wb");
+	if (sigs == NULL) {
+		fprintf(stderr, "Couldn't open %s\n", tmppath);
+		return;
+	}
+	free(tmppath);
+
+	config.dbbackend->cached_getkeysigs(keyid);
+	curkey = findinhash(keyid);
+	curkey->colour = ++curidx;
+	pending = lladd(NULL, curkey);
+
+	output_key(names, keys, curkey->keyid);
+
+	while (pending != NULL) {
+		curkey = (struct stats_key *) pending->object;
+		sigll = config.dbbackend->cached_getkeysigs(curkey->keyid);
+		sigsave = sigll = sortkeyll(sigll);
+		sigcount = 0;
+		while (sigll != NULL) {
+			addkey = (struct stats_key *) sigll->object;
+			if (addkey->colour == 0) {
+				uid = config.dbbackend->keyid2uid(addkey->keyid);
+				if (uid != NULL) {
+					addkey->colour = ++curidx;
+					pending = lladdend(pending, addkey);
+					output_key(names, keys, addkey->keyid);
+				}
+			}
+			if (addkey->colour != 0) {
+				sigcount++;
+			}
+			sigll = sigll->next;
+		}
+		/* Now output the signatures */
+		sigcount = htonl(sigcount);
+		fwrite(&sigcount, sizeof (sigcount), 1, sigs);
+		sigll = sigsave;
+		while (sigll != NULL) {
+			addkey = (struct stats_key *) sigll->object;
+			if (addkey->colour != 0) {
+				sigentry = addkey->colour - 1;
+				/* Pretend it's on the primary UID for now */
+				sigentry |= 0x40000000;
+				sigentry = htonl(sigentry);
+				fwrite(&sigentry, sizeof (sigentry), 1, sigs);
+			}
+			sigll = sigll->next;
+		}
+		pending = pending->next;
+	}
+
+	fclose(sigs);
+	fclose(keys);
+	fclose(names);
+}
+
+int main(int argc, char *argv[])
+{
+	int optchar;
+	char *configfile = NULL, *dir = NULL;
+	uint64_t keyid = 0x2DA8B985;
+
+	while ((optchar = getopt(argc, argv, "c:")) != -1 ) {
+		switch (optchar) {
+		case 'c':
+			configfile = strdup(optarg);
+			break;
+		}
+	}
+
+	if (optind < argc) {
+		dir = argv[optind];
+	}
+
+	readconfig(configfile);
+	initlogthing("wotsap", config.logfile);
+	config.dbbackend->initdb(true);
+	inithash();
+	wotsap(config.dbbackend->getfullkeyid(keyid), dir ? dir : ".");
+	destroyhash();
+	config.dbbackend->cleanupdb();
+	cleanuplogthing();
+	cleanupconfig();
+}
-- 
2.39.5