From: Jonathan McDowell Date: Mon, 31 May 2004 23:46:54 +0000 (+0000) Subject: cscvs to tla changeset 3 X-Git-Url: https://git.sommitrealweird.co.uk/onak.git/commitdiff_plain/e02c731dfbb288c736f2cd09a9b6df0507c59ddd?ds=inline cscvs to tla changeset 3 Author: noodles Date: 2002/09/08 09:16:22 Committing onak 0.0.3. --- diff --git a/HISTORY b/HISTORY index e72f3df..8cc8eb2 100644 --- a/HISTORY +++ b/HISTORY @@ -21,3 +21,22 @@ * Added searching on uids to lookup & keydb_pg. * Changed Postgres backend to use 64 bit keyids instead of 32 bit. * Made dearmor ignore CRs when looking for 2 blank lines. + +0.0.3 - 2nd July 2002 + +* Added index on keyid for key table in Postgres backend. +* Twiddled transaction support in an attempt to speed up mass key adding. +* Changed putchar_func to take a character count rather than doing one char at + a time (massive speed up in Postgres case). +* Introduced onak binary for general keyserver operations. +* Changed all version number references to use VERSION macro. +* Made lldel free the unused list structure. (pointed out by Simon Huggins) +* Made llfind assert that the cmp function is non NULL. (Simon Huggins again) +* Fixed gpgwww; after the move to 64bit keyids internally it was trying to + compare the 32bit user supplied keyid to the retrieved 64bit one and never + finding paths. +* Various tidying up I've forgotten. +* Fixed bug with removing signed packets while merging. +* Fixed bug with potentially adding an already existing uid to a key when + merging. +* Fixed stupid typo bug in merging keys. diff --git a/Makefile b/Makefile index 115ebe0..9f432b8 100644 --- a/Makefile +++ b/Makefile @@ -2,27 +2,25 @@ CC = gcc CFLAGS += -Wall -pedantic -g -I/usr/local/include +#LDFLAGS += -pg # Can be "pg" for Postgresql, "file" for flat files or "db2" for pksd db2 style. DBTYPE = pg # If using DBTYPE of "file" then comment the following line out. LIBS = -L/usr/local/lib -lpq #LIBS = -L/usr/local/lib -ldb2 -PROGS = keymerge add lookup gpgwww +PROGS = add lookup gpgwww onak OBJS = armor.o parsekey.o keydb_$(DBTYPE).o merge.o keyid.o md5.o sha.o \ - getcgi.o keyindex.o mem.o stats.o ll.o hash.o onak_conf.o + getcgi.o keyindex.o mem.o stats.o ll.o hash.o onak-conf.o SRCS = armor.c parsekey.c merge.c keyid.c md5.c sha.c main.c getcgi.c stats.c \ keyindex.c mem.c lookup.c add.c keydb_$(DBTYPE).c ll.c hash.c \ - pathtest.c gpgwww.c onak_conf.c + gpgwww.c onak-conf.c -all: $(PROGS) testparse pathtest maxpath +all: $(PROGS) testparse maxpath testparse: main.o $(OBJS) $(CC) -o testparse main.o $(OBJS) $(LIBS) -pathtest: pathtest.o $(OBJS) - $(CC) -o pathtest pathtest.o $(OBJS) $(LIBS) - maxpath: maxpath.o $(OBJS) $(CC) -o maxpath maxpath.o $(OBJS) $(LIBS) @@ -30,35 +28,30 @@ gpgwww: gpgwww.o $(OBJS) $(CC) -o gpgwww gpgwww.o $(OBJS) $(LIBS) lookup: lookup.o getcgi.o keyindex.o keydb_$(DBTYPE).o keyid.o sha.o \ - parsekey.o mem.o armor.o ll.o hash.o onak_conf.o + parsekey.o mem.o armor.o ll.o hash.o onak-conf.o $(CC) -o lookup lookup.o getcgi.o keyindex.o keydb_$(DBTYPE).o keyid.o \ - sha.o parsekey.o mem.o armor.o ll.o hash.o onak_conf.o $(LIBS) + sha.o parsekey.o mem.o armor.o ll.o hash.o onak-conf.o $(LIBS) add: add.o getcgi.o armor.o parsekey.o keydb_$(DBTYPE).o keyid.o sha.o mem.o \ - keyindex.o ll.o hash.o merge.o onak_conf.o + keyindex.o ll.o hash.o merge.o onak-conf.o $(CC) -o add add.o getcgi.o armor.o parsekey.o keydb_$(DBTYPE).o \ - keyid.o sha.o mem.o keyindex.o ll.o hash.o merge.o onak_conf.o \ + keyid.o sha.o mem.o keyindex.o ll.o hash.o merge.o onak-conf.o \ $(LIBS) -keymerge: keymerge.o merge.o keyid.o sha.o armor.o parsekey.o ll.o \ - keydb_$(DBTYPE).o mem.o keyindex.o hash.o getcgi.o onak_conf.o - $(CC) -o keymerge keymerge.o merge.o keyid.o sha.o armor.o parsekey.o \ - keydb_$(DBTYPE).o mem.o keyindex.o ll.o hash.o getcgi.o \ - onak_conf.o $(LIBS) - onak: onak.o merge.o keyid.o sha.o armor.o parsekey.o ll.o \ - keydb_$(DBTYPE).o mem.o keyindex.o hash.o getcgi.o onak_conf.o - $(CC) -o onak onak.o merge.o keyid.o sha.o armor.o parsekey.o \ + keydb_$(DBTYPE).o mem.o keyindex.o hash.o getcgi.o onak-conf.o + $(CC) $(LDFLAGS) -o onak onak.o merge.o keyid.o sha.o armor.o parsekey.o \ keydb_$(DBTYPE).o mem.o keyindex.o ll.o hash.o getcgi.o \ - onak_conf.o $(LIBS) + onak-conf.o $(LIBS) clean: - rm -f $(PROGS) $(OBJS) Makefile.bak testparse pathtest maxpath \ - *.core core \ - keymerge.o pathtest.o gpgwww.o add.o lookup.o main.o maxpath.o + rm -f $(PROGS) $(OBJS) Makefile.bak testparse maxpath *.core core \ + gpgwww.o add.o lookup.o main.o maxpath.o onak.o depend: makedepend $(SRCS) +keydb_pg.o: keydb.o + # DO NOT DELETE diff --git a/PERFORMANCE b/PERFORMANCE new file mode 100644 index 0000000..3221831 --- /dev/null +++ b/PERFORMANCE @@ -0,0 +1,39 @@ +Performance of adding 2M key chunks (~ 1700 keys). + +Originally ~ 30 mins with 0.0.2. + +After adding an index on keyid for onak_keys: + Command being timed: "/u2/noodles/onak-0.0.3/onak" + User time (seconds): 75.12 + System time (seconds): 76.08 + Percent of CPU this job got: 3% + Elapsed (wall clock) time (h:mm:ss or m:ss): 1:18:17 + Major (requiring I/O) page faults: 630 + Minor (reclaiming a frame) page faults: 1238 + +Making deletion in the same transaction as readding: + Command being timed: "/u2/noodles/onak-0.0.3/onak" + User time (seconds): 67.28 + System time (seconds): 75.74 + Percent of CPU this job got: 2% + Elapsed (wall clock) time (h:mm:ss or m:ss): 1:22:08 + Major (requiring I/O) page faults: 617 + Minor (reclaiming a frame) page faults: 1241 + +Making merge_keys all one transaction: + Command being timed: "/u2/noodles/onak-0.0.3/onak" + User time (seconds): 74.45 + System time (seconds): 69.82 + Percent of CPU this job got: 2% + Elapsed (wall clock) time (h:mm:ss or m:ss): 1:29:28 + Major (requiring I/O) page faults: 610 + Minor (reclaiming a frame) page faults: 1237 + +Only delete old key if we know it exists: + Command being timed: "/u2/noodles/onak-0.0.3/onak" + User time (seconds): 77.47 + System time (seconds): 75.06 + Percent of CPU this job got: 3% + Elapsed (wall clock) time (h:mm:ss or m:ss): 1:16:41 + Major (requiring I/O) page faults: 610 + Minor (reclaiming a frame) page faults: 1239 diff --git a/TODO b/TODO index 72dc698..d06b23b 100644 --- a/TODO +++ b/TODO @@ -2,10 +2,12 @@ * Check keys on import? * Test library? * Better signature subpacket parsing (primary UID for example). +* Better merging of signatures; need to decode subpackets so we can make sure + we don't add a signature that's already on a key but has different + subpackets. * Better txt2html routine. * Remove bithelp.h (i386 only at present & inlined). - Build and test on non-i386. -* BSD license? (Needs md5 & sha routines rewritten/replaced then) * Pathfinder - graphical as well? Multiple paths? * Do pathlengths for similar email addresses to help aide keysigning. (ie "Find me the keys furthest from mine that end ox.ac.uk'") @@ -22,8 +24,9 @@ * Change over to PQescapeString in PostgreSQL backend once Woody releases. * Check freeing. * More comments. -* Sort out merging (use keymerge + some Perl to answer incoming email. Not - sure about keys via hkp yet though). +* Sort out merging (use onak + some Perl to answer incoming email. Not + sure about keys via hkp yet though - need to send updates out). * Look at db2 backend - is it db2's fault? (Well, deadlock in that the library probably is...). Is there an alternative library that would provide us with similar features but be more reliable? gdbm? +* Honor no-modify keyserver flag ("Brian M. Carlson" ) diff --git a/add-imp.pl b/add-imp.pl new file mode 100755 index 0000000..09470c3 --- /dev/null +++ b/add-imp.pl @@ -0,0 +1,12 @@ +#!/usr/bin/perl + +while (<>) { + /(........)$/; + print "Attempting to get $1\n"; + $key = `./onak-db2 get $1`; + open (ONAK, "| ./onak -v add"); + print ONAK $key; + close ONAK; + + sleep 60; +} diff --git a/add.c b/add.c index d820b52..3af9309 100644 --- a/add.c +++ b/add.c @@ -66,7 +66,7 @@ int main(int argc, char *argv[]) parse_keys(packets, &keys); initdb(); printf("Got %d new keys.\n", - update_keys(&keys)); + update_keys(&keys, false)); cleanupdb(); } else { puts("No OpenPGP packets found in input."); diff --git a/armor.c b/armor.c index d811e50..9f5c19a 100644 --- a/armor.c +++ b/armor.c @@ -11,6 +11,7 @@ #include "armor.h" #include "keystructs.h" +#include "onak-conf.h" #include "parsekey.h" #define ARMOR_WIDTH 64 @@ -62,21 +63,6 @@ static unsigned char decode64(unsigned char c) { return c; } - -void putstring(int (*putchar_func)(void *ctx, unsigned char c), - void *ctx, - const char *string) -{ - int i; - - assert(putchar_func != NULL); - assert(string != NULL); - - for (i = 0; string[i] != 0; i++) { - putchar_func(ctx, string[i]); - } -} - /** * @lastoctet: The last octet we got. * @curoctet: The current octet we're expecting (0, 1 or 2). @@ -90,7 +76,7 @@ struct armor_context { int curoctet; int count; long crc24; - int (*putchar_func)(void *ctx, unsigned char c); + int (*putchar_func)(void *ctx, size_t count, unsigned char *c); void *ctx; }; @@ -104,36 +90,44 @@ static void armor_init(struct armor_context *ctx) static void armor_finish(struct armor_context *state) { + unsigned char c; + switch (state->curoctet++) { case 0: break; case 1: - state->putchar_func(state->ctx, - encode64((state->lastoctet & 3) << 4)); - state->putchar_func(state->ctx, '='); - state->putchar_func(state->ctx, '='); + c = encode64((state->lastoctet & 3) << 4); + state->putchar_func(state->ctx, 1, &c); + state->putchar_func(state->ctx, 1, (unsigned char *) "="); + state->putchar_func(state->ctx, 1, (unsigned char *) "="); break; case 2: - state->putchar_func(state->ctx, - encode64((state->lastoctet & 0xF) << 2)); - state->putchar_func(state->ctx, '='); + c = encode64((state->lastoctet & 0xF) << 2); + state->putchar_func(state->ctx, 1, &c); + state->putchar_func(state->ctx, 1, (unsigned char *) "="); break; } state->crc24 &= 0xffffffL; - state->putchar_func(state->ctx, '\n'); - state->putchar_func(state->ctx, '='); - state->putchar_func(state->ctx, encode64(state->crc24 >> 18)); - state->putchar_func(state->ctx, encode64((state->crc24 >> 12) & 0x3F)); - state->putchar_func(state->ctx, encode64((state->crc24 >> 6) & 0x3F)); - state->putchar_func(state->ctx, encode64(state->crc24 & 0x3F)); - state->putchar_func(state->ctx, '\n'); + state->putchar_func(state->ctx, 1, (unsigned char *) "\n"); + state->putchar_func(state->ctx, 1, (unsigned char *) "="); + c = encode64(state->crc24 >> 18); + state->putchar_func(state->ctx, 1, &c); + c = encode64((state->crc24 >> 12) & 0x3F); + state->putchar_func(state->ctx, 1, &c); + c = encode64((state->crc24 >> 6) & 0x3F); + state->putchar_func(state->ctx, 1, &c); + c = encode64(state->crc24 & 0x3F); + state->putchar_func(state->ctx, 1, &c); + state->putchar_func(state->ctx, 1, (unsigned char *) "\n"); } -static int armor_putchar(void *ctx, unsigned char c) + +static int armor_putchar_int(void *ctx, unsigned char c) { struct armor_context *state; + unsigned char t; int i; assert(ctx != NULL); @@ -141,18 +135,20 @@ static int armor_putchar(void *ctx, unsigned char c) switch (state->curoctet++) { case 0: - state->putchar_func(state->ctx, encode64(c >> 2)); + t = encode64(c >> 2); + state->putchar_func(state->ctx, 1, &t); state->count++; break; case 1: - state->putchar_func(state->ctx, - encode64(((state->lastoctet & 3) << 4) + (c >> 4))); + t = encode64(((state->lastoctet & 3) << 4) + (c >> 4)); + state->putchar_func(state->ctx, 1, &t); state->count++; break; case 2: - state->putchar_func(state->ctx, - encode64(((state->lastoctet & 0xF) << 2) + (c >> 6))); - state->putchar_func(state->ctx, encode64(c & 0x3F)); + t = encode64(((state->lastoctet & 0xF) << 2) + (c >> 6)); + state->putchar_func(state->ctx, 1, &t); + t = encode64(c & 0x3F); + state->putchar_func(state->ctx, 1, &t); state->count += 2; break; } @@ -168,12 +164,24 @@ static int armor_putchar(void *ctx, unsigned char c) } if ((state->count % ARMOR_WIDTH) == 0) { - state->putchar_func(state->ctx, '\n'); + state->putchar_func(state->ctx, 1, (unsigned char *) "\n"); } return 0; } + +static int armor_putchar(void *ctx, size_t count, unsigned char *c) +{ + int i; + + for (i = 0; i < count; i++) { + armor_putchar_int(ctx, c[i]); + } + + return 0; +} + /** * @lastoctet: The last octet we got. * @curoctet: The current octet we're expecting (0, 1 or 2). @@ -285,18 +293,20 @@ static int dearmor_getchar_c(void *ctx, size_t count, unsigned char *c) * This function ASCII armors a list of OpenPGP packets and outputs it * using putchar_func. */ -int armor_openpgp_stream(int (*putchar_func)(void *ctx, unsigned char c), +int armor_openpgp_stream(int (*putchar_func)(void *ctx, size_t count, + unsigned char *c), void *ctx, struct openpgp_packet_list *packets) { struct armor_context armor_ctx; - /* * Print armor header */ - putstring(putchar_func, ctx, "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"); - putstring(putchar_func, ctx, "Version: onak 0.0.1\n\n"); + putchar_func(ctx, sizeof("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"), + (unsigned char *) "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"); + putchar_func(ctx, sizeof("Version: onak " VERSION "\n\n"), + (unsigned char *) "Version: onak " VERSION "\n\n"); armor_init(&armor_ctx); armor_ctx.putchar_func = putchar_func; @@ -307,7 +317,8 @@ int armor_openpgp_stream(int (*putchar_func)(void *ctx, unsigned char c), /* * Print armor footer */ - putstring(putchar_func, ctx, "-----END PGP PUBLIC KEY BLOCK-----\n"); + putchar_func(ctx, sizeof("-----END PGP PUBLIC KEY BLOCK-----\n"), + (unsigned char *) "-----END PGP PUBLIC KEY BLOCK-----\n"); return 0; } diff --git a/armor.h b/armor.h index 6b58a34..85ac6a6 100644 --- a/armor.h +++ b/armor.h @@ -20,7 +20,8 @@ * This function ASCII armors a list of OpenPGP packets and outputs it * using putchar_func. */ -int armor_openpgp_stream(int (*putchar_func)(void *ctx, unsigned char c), +int armor_openpgp_stream(int (*putchar_func)(void *ctx, size_t count, + unsigned char *c), void *ctx, struct openpgp_packet_list *packets); diff --git a/gpgwww.c b/gpgwww.c index 5f7da46..dc79135 100644 --- a/gpgwww.c +++ b/gpgwww.c @@ -14,7 +14,7 @@ #include "getcgi.h" #include "hash.h" #include "keydb.h" -#include "onak_conf.h" +#include "onak-conf.h" #include "stats.h" void dofindpath(uint64_t have, uint64_t want, bool html) @@ -23,6 +23,9 @@ void dofindpath(uint64_t have, uint64_t want, bool html) int rec; char *uid; + have = getfullkeyid(have); + want = getfullkeyid(want); + /* * Make sure the keys we have and want are in the cache. */ @@ -53,30 +56,31 @@ void dofindpath(uint64_t have, uint64_t want, bool html) want); } else { printf("%d steps from 0x%llX to 0x%llX\n", - keyinfoa->colour, have, want); + keyinfoa->colour, have & 0xFFFFFFFF, + want & 0xFFFFFFFF); curkey = keyinfoa; while (curkey != NULL && curkey->keyid != 0) { uid = keyid2uid(curkey->keyid); if (html && uid == NULL) { printf("" "0x%llX ([User id not found])%s)%s\n", - curkey->keyid, - curkey->keyid, + curkey->keyid & 0xFFFFFFFF, + curkey->keyid & 0xFFFFFFFF, (curkey->keyid == want) ? "" : " signs"); } else if (html && uid != NULL) { printf("" "0x%llX (%s)%s\n", - curkey->keyid, - curkey->keyid, - curkey->keyid, + curkey->keyid & 0xFFFFFFFF, + curkey->keyid & 0xFFFFFFFF, + curkey->keyid & 0xFFFFFFFF, txt2html(keyid2uid(curkey->keyid)), (curkey->keyid == want) ? "" : " signs"); } else { printf("0x%llX (%s)%s\n", - curkey->keyid, + curkey->keyid & 0xFFFFFFFF, (uid == NULL) ? "[User id not found]" : uid, (curkey->keyid == want) ? "" : diff --git a/hash.c b/hash.c index 82dfcc7..47ec2d8 100644 --- a/hash.c +++ b/hash.c @@ -11,6 +11,7 @@ #include "hash.h" #include "keydb.h" +#include "keyid.h" #include "ll.h" #include "stats.h" diff --git a/keydb.c b/keydb.c index a9e4cef..3ebb056 100644 --- a/keydb.c +++ b/keydb.c @@ -35,7 +35,7 @@ char *keyid2uid(uint64_t keyid) static char buf[1024]; buf[0]=0; - if (fetch_key(keyid, &publickey) && publickey != NULL) { + if (fetch_key(keyid, &publickey, false) && publickey != NULL) { curuid = publickey->uids; while (curuid != NULL && buf[0] == 0) { if (curuid->packet->tag == 13) { @@ -70,7 +70,7 @@ struct ll *getkeysigs(uint64_t keyid) struct openpgp_signedpacket_list *uids = NULL; struct openpgp_publickey *publickey = NULL; - fetch_key(keyid, &publickey); + fetch_key(keyid, &publickey, false); if (publickey != NULL) { for (uids = publickey->uids; uids != NULL; uids = uids->next) { @@ -82,3 +82,25 @@ struct ll *getkeysigs(uint64_t keyid) return sigs; } #endif + +#ifdef NEED_GETFULLKEYID +/** + * getfullkeyid - Maps a 32bit key id to a 64bit one. + * @keyid: The 32bit keyid. + * + * This function maps a 32bit key id to the full 64bit one. It returns the + * full keyid. + */ +uint64_t getfullkeyid(uint64_t keyid) +{ + struct openpgp_publickey *publickey = NULL; + + if (keyid < 0x100000000) { + fetch_key(keyid, &publickey, false); + keyid = get_keyid(publickey); + free_publickey(publickey); + } + + return keyid; +} +#endif diff --git a/keydb.h b/keydb.h index 9a248e3..e9bcb67 100644 --- a/keydb.h +++ b/keydb.h @@ -32,38 +32,60 @@ void initdb(void); */ void cleanupdb(void); +/** + * 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); + +/** + * endtrans - End a transaction. + * + * Ends a transaction. + */ +void endtrans(void); + /** * 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. * * This function returns a public key from whatever storage mechanism we * are using. * * TODO: What about keyid collisions? Should we use fingerprint instead? */ -int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey); +int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey, bool intrans); /** * 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. * * This function stores a public key in whatever storage mechanism we are - * using. + * using. intrans indicates if we're already in a transaction so don't + * need to start one. update indicates if the key already exists and is + * just being updated. * * TODO: Do we store multiple keys of the same id? Or only one and replace * it? */ -int store_key(struct openpgp_publickey *publickey); +int store_key(struct openpgp_publickey *publickey, bool intrans, bool update); /** * 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); +int delete_key(uint64_t keyid, bool intrans); /** * fetch_key_text - Trys to find the keys that contain the supplied text. @@ -93,4 +115,13 @@ char *keyid2uid(uint64_t keyid); */ struct ll *getkeysigs(uint64_t keyid); +/** + * getfullkeyid - Maps a 32bit key id to a 64bit one. + * @keyid: The 32bit keyid. + * + * This function maps a 32bit key id to the full 64bit one. It returns the + * full keyid. + */ +uint64_t getfullkeyid(uint64_t keyid); + #endif /* __KEYDB_H__ */ diff --git a/keydb_db2.c b/keydb_db2.c index 771b2a5..3d66c5a 100644 --- a/keydb_db2.c +++ b/keydb_db2.c @@ -21,7 +21,7 @@ #include "keyindex.h" #include "keystructs.h" #include "mem.h" -#include "onak_conf.h" +#include "onak-conf.h" #include "parsekey.h" #define KEYDB_KEYID_BYTES 4 @@ -155,10 +155,33 @@ void cleanupdb(void) 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 @@ -166,7 +189,8 @@ void cleanupdb(void) * in and then parse_keys() to parse the packets into a publickey * structure. */ -int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey) +int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey, + bool intrans) { struct openpgp_packet_list *packets = NULL; int ret; @@ -187,7 +211,6 @@ int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey) ret = (*(keydb(&key)->get))(keydb(&key), NULL, &key, &data, 0); if (ret == 0) { - //do stuff with data. fetchbuf.buffer = data.data; fetchbuf.offset = 0; fetchbuf.size = data.size; @@ -198,16 +221,31 @@ int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey) 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) +int store_key(struct openpgp_publickey *publickey, bool intrans, bool update) { return 0; } @@ -215,11 +253,12 @@ int store_key(struct openpgp_publickey *publickey) /** * 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) +int delete_key(uint64_t keyid, bool intrans) { return (1); } @@ -229,4 +268,5 @@ int delete_key(uint64_t keyid) */ #define NEED_KEYID2UID 1 #define NEED_GETKEYSIGS 1 +#define NEED_GETFULLKEYID 1 #include "keydb.c" diff --git a/keydb_file.c b/keydb_file.c index 9784ecf..1d9b081 100644 --- a/keydb_file.c +++ b/keydb_file.c @@ -20,7 +20,7 @@ #include "keystructs.h" #include "ll.h" #include "mem.h" -#include "onak_conf.h" +#include "onak-conf.h" #include "parsekey.h" /** @@ -34,9 +34,9 @@ static int keydb_fetchchar(void *fd, size_t count, unsigned char *c) /** * keydb_putchar - Puts a char to a file. */ -static int keydb_putchar(void *fd, unsigned char c) +static int keydb_putchar(void *fd, size_t count, unsigned char *c) { - return !(write( *(int *) fd, &c, sizeof(c))); + return !(write( *(int *) fd, c, count)); } /** @@ -143,4 +143,5 @@ int delete_key(uint64_t keyid) */ #define NEED_KEYID2UID 1 #define NEED_GETKEYSIGS 1 +#define NEED_GETFULLKEYID 1 #include "keydb.c" diff --git a/keydb_pg.c b/keydb_pg.c index d2a2a55..3f519bc 100644 --- a/keydb_pg.c +++ b/keydb_pg.c @@ -25,7 +25,7 @@ #include "keyindex.h" #include "keystructs.h" #include "mem.h" -#include "onak_conf.h" +#include "onak-conf.h" #include "parsekey.h" /** @@ -44,9 +44,9 @@ static int keydb_fetchchar(void *fd, size_t count, unsigned char *c) /** * keydb_putchar - Puts a char to a file. */ -static int keydb_putchar(void *fd, unsigned char c) +static int keydb_putchar(void *fd, size_t count, unsigned char *c) { - return !(lo_write(dbconn, *(int *) fd, &c, sizeof(c))); + return !(lo_write(dbconn, *(int *) fd, c, count)); } /** @@ -87,10 +87,43 @@ void cleanupdb(void) dbconn = NULL; } +/** + * 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) +{ + PGresult *result = NULL; + + result = PQexec(dbconn, "BEGIN"); + PQclear(result); + + return true; +} + +/** + * endtrans - End a transaction. + * + * Ends a transaction. + */ +void endtrans(void) +{ + PGresult *result = NULL; + + result = PQexec(dbconn, "COMMIT"); + PQclear(result); + + 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 @@ -98,7 +131,7 @@ void cleanupdb(void) * in and then parse_keys() to parse the packets into a publickey * structure. */ -int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey) +int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey, bool intrans) { struct openpgp_packet_list *packets = NULL; PGresult *result = NULL; @@ -109,8 +142,10 @@ int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey) int numkeys = 0; Oid key_oid; - result = PQexec(dbconn, "BEGIN"); - PQclear(result); + if (!intrans) { + result = PQexec(dbconn, "BEGIN"); + PQclear(result); + } if (keyid > 0xFFFFFFFF) { snprintf(statement, 1023, @@ -146,8 +181,10 @@ int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey) PQclear(result); - result = PQexec(dbconn, "COMMIT"); - PQclear(result); + if (!intrans) { + result = PQexec(dbconn, "COMMIT"); + PQclear(result); + } return (numkeys); } @@ -228,13 +265,16 @@ int fetch_key_text(const char *search, struct openpgp_publickey **publickey) /** * 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. + * the file. If update is true then we delete the old key first, otherwise we + * trust that it doesn't exist. */ -int store_key(struct openpgp_publickey *publickey) +int store_key(struct openpgp_publickey *publickey, bool intrans, bool update) { struct openpgp_packet_list *packets = NULL; struct openpgp_packet_list *list_end = NULL; @@ -248,6 +288,10 @@ int store_key(struct openpgp_publickey *publickey) char *dodgychar = NULL; int i; + if (!intrans) { + result = PQexec(dbconn, "BEGIN"); + PQclear(result); + } /* * Delete the key if we already have it. @@ -257,10 +301,9 @@ int store_key(struct openpgp_publickey *publickey) * of difference though - the largest chunk of data is the keydata and * it definitely needs updated. */ - delete_key(get_keyid(publickey)); - - result = PQexec(dbconn, "BEGIN"); - PQclear(result); + if (update) { + delete_key(get_keyid(publickey), true); + } next = publickey->next; publickey->next = NULL; @@ -328,8 +371,10 @@ int store_key(struct openpgp_publickey *publickey) } } - result = PQexec(dbconn, "COMMIT"); - PQclear(result); + if (!intrans) { + result = PQexec(dbconn, "COMMIT"); + PQclear(result); + } return 0; } @@ -337,11 +382,12 @@ int store_key(struct openpgp_publickey *publickey) /** * 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) +int delete_key(uint64_t keyid, bool intrans) { PGresult *result = NULL; char *oids = NULL; @@ -350,8 +396,10 @@ int delete_key(uint64_t keyid) int i; Oid key_oid; - result = PQexec(dbconn, "BEGIN"); - PQclear(result); + if (!intrans) { + result = PQexec(dbconn, "BEGIN"); + PQclear(result); + } snprintf(statement, 1023, "SELECT keydata FROM onak_keys WHERE keyid = '%llX'", @@ -386,8 +434,10 @@ int delete_key(uint64_t keyid) PQclear(result); - result = PQexec(dbconn, "COMMIT"); - PQclear(result); + if (!intrans) { + result = PQexec(dbconn, "COMMIT"); + PQclear(result); + } return (found); } @@ -430,4 +480,5 @@ char *keyid2uid(uint64_t keyid) * Include the basic keydb routines. */ #define NEED_GETKEYSIGS 1 +#define NEED_GETFULLKEYID 1 #include "keydb.c" diff --git a/keyid.c b/keyid.c index 8131e16..65a0363 100644 --- a/keyid.c +++ b/keyid.c @@ -26,6 +26,8 @@ uint64_t get_keyid(struct openpgp_publickey *publickey) unsigned char c; unsigned char *buff = NULL; + assert(publickey != NULL); + switch (publickey->publickey->data[0]) { case 2: case 3: diff --git a/keyindex.c b/keyindex.c index dfa0d44..a1bbe2e 100644 --- a/keyindex.c +++ b/keyindex.c @@ -237,7 +237,7 @@ int key_index(struct openpgp_publickey *keys, bool verbose, bool fingerprint, if (html) { puts("
");
 	}
-	puts("Type  bits/keyID    Date       User ID");
+	puts("Type   bits/keyID    Date       User ID");
 	while (keys != NULL) {
 		created_time = (keys->publickey->data[1] << 24) +
 					(keys->publickey->data[2] << 16) +
diff --git a/keymerge.c b/keymerge.c
deleted file mode 100644
index fb95c2d..0000000
--- a/keymerge.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * keymerge.c - Takes a key on stdin, merges it and outputs the difference.
- *
- * Jonathan McDowell 
- * 
- * Copyright 2002 Project Purple
- */
-
-#include 
-#include 
-
-#include "armor.h"
-#include "keydb.h"
-#include "keyid.h"
-#include "keystructs.h"
-#include "mem.h"
-#include "merge.h"
-#include "parsekey.h"
-
-int stdin_getchar(void *ctx, size_t count, unsigned char *c)
-{
-	int ic;
-
-	do {
-		ic = getchar();
-		*c = ic;
-		c++;
-	} while ((ic != EOF) && (--count > 0));
-	return (ic == EOF);
-}
-
-int stdout_putchar(void *ctx, unsigned char c)
-{
-	return (putchar(c));
-}
-
-
-int main(int argc, char *argv[])
-{
-	struct openpgp_packet_list	*packets = NULL;
-	struct openpgp_packet_list	*list_end = NULL;
-	struct openpgp_publickey	*keys = NULL;
-	int				 rc = EXIT_SUCCESS;
-
-	dearmor_openpgp_stream(stdin_getchar, NULL, &packets);
-	if (packets != NULL) {
-		parse_keys(packets, &keys);
-		free_packet_list(packets);
-		packets = NULL;
-
-		initdb();
-		fprintf(stderr, "Got %d new keys.\n",
-				update_keys(&keys));
-		cleanupdb();
-	} else {
-		rc = 1;
-		fprintf(stderr, "No keys read.\n");
-	}
-
-	if (keys != NULL) {
-		flatten_publickey(keys, &packets, &list_end);
-		free_publickey(keys);
-		keys = NULL;
-
-		armor_openpgp_stream(stdout_putchar, NULL, packets);
-		free_packet_list(packets);
-		packets = NULL;
-	} else {
-		rc = 1;
-		fprintf(stderr, "No changes.\n");
-	}
-
-	return rc;
-}
diff --git a/ll.c b/ll.c
index acad0bc..7110378 100644
--- a/ll.c
+++ b/ll.c
@@ -31,7 +31,8 @@ struct ll *lladd(struct ll *curll, void *object)
 struct ll *lldel(struct ll *curll, void *object,
 	int (*objectcmp) (const void *object1, const void *object2))
 {
-	struct ll *cur;
+	struct ll *cur = NULL;
+	struct ll *old = NULL;
 
 	assert(objectcmp != NULL);
 
@@ -39,11 +40,16 @@ struct ll *lldel(struct ll *curll, void *object,
 	if (cur == NULL) {
 		return NULL;
 	} else if (!(*objectcmp)(cur->object, object)) {
-		return cur->next;
+		old = cur;
+		cur = cur->next;
+		free(old);
+		return cur;
 	} 
 	while (cur->next != NULL) {
 		if (!(*objectcmp)(cur->next->object, object)) {
+			old = cur->next;
 			cur->next = cur->next->next;
+			free(old);
 			break;
 		}
 	}
@@ -55,6 +61,8 @@ struct ll *llfind(struct ll *curll, void *object,
 {
 	struct ll *cur;
 
+	assert(objectcmp != NULL);
+
 	cur = curll;
 	while (cur != NULL && (*objectcmp)(cur->object, object)) {
 		cur = cur->next;
diff --git a/lookup.c b/lookup.c
index 3c55a61..94f38c8 100644
--- a/lookup.c
+++ b/lookup.c
@@ -18,7 +18,7 @@
 #include "keydb.h"
 #include "keyindex.h"
 #include "mem.h"
-#include "onak_conf.h"
+#include "onak-conf.h"
 #include "parsekey.h"
 
 #define OP_UNKNOWN 0
@@ -26,9 +26,9 @@
 #define OP_INDEX   2
 #define OP_VINDEX  3
 
-int putnextchar(void *ctx, unsigned char c)
+int putnextchar(void *ctx, size_t count, unsigned char *c)
 {
-        return putchar(c);
+	return printf("%.*s", count, c);
 }
 
 void find_keys(char *search, uint64_t keyid, bool ishex,
@@ -38,7 +38,7 @@ void find_keys(char *search, uint64_t keyid, bool ishex,
 	int count = 0;
 
 	if (ishex) {
-		count = fetch_key(keyid, &publickey);
+		count = fetch_key(keyid, &publickey, false);
 	} else {
 		count = fetch_key_text(search, &publickey);
 	}
@@ -115,7 +115,7 @@ int main(int argc, char *argv[])
 		initdb();
 		switch (op) {
 		case OP_GET:
-			if (fetch_key(keyid, &publickey)) {
+			if (fetch_key(keyid, &publickey, false)) {
 				puts("
");
 				flatten_publickey(publickey,
 							&packets,
diff --git a/maxpath.c b/maxpath.c
index 3714281..f46ea4c 100644
--- a/maxpath.c
+++ b/maxpath.c
@@ -24,7 +24,7 @@ void findmaxpath(unsigned long max)
 	printf("In findmaxpath\n");
 	distance = 0;
 	from = to = tmp = NULL;
-	hash_getkeysigs(0x5B430367);
+	hash_getkeysigs(0xF1BD4BE45B430367);
 
 	for (loop = 0; (loop < HASHSIZE) && (distance < max); loop++) {
 		curkey = gethashtableentry(loop);
diff --git a/merge.c b/merge.c
index 96e628b..8bfbd09 100644
--- a/merge.c
+++ b/merge.c
@@ -105,7 +105,9 @@ bool remove_signed_packet(struct openpgp_signedpacket_list **packet_list,
 			if (cur->next == NULL) {
 				*list_end = prev;
 			}
+			// TODO: Free the removed signed packet...
 		}
+		prev = cur;
 	}
 
 	return found;
@@ -192,25 +194,29 @@ int merge_signed_packets(struct openpgp_signedpacket_list **old,
 			if (newelem->sigs == NULL) {
 				remove_signed_packet(new,
 						new_end,
-						curelem->packet);
+						newelem->packet);
 			}
 		}
 	}
 
 	/*
-	 * If *new != NULL now then there are UIDs on the new key that weren't
-	 * on the old key. Add them.
+	 * If *new != NULL now then there might be UIDs on the new key that
+	 * weren't on the old key. Walk through them, checking if the UID is
+	 * on the old key and if not adding them to it.
 	 */
 	for (curelem = *new; curelem != NULL;
 			curelem = curelem->next) {
-		ADD_PACKET_TO_LIST((*old_end),
+
+		if (find_signed_packet(*old, curelem->packet) == NULL) {
+			ADD_PACKET_TO_LIST((*old_end),
 				packet_dup(curelem->packet));
-		if (*old == NULL) {
-			*old = *old_end;
-		}
-		packet_list_add(&(*old_end)->sigs,
+			if (*old == NULL) {
+				*old = *old_end;
+			}
+			packet_list_add(&(*old_end)->sigs,
 				&(*old_end)->last_sig,
 				curelem->sigs);
+		}
 	}
 
 	return 0;
@@ -290,7 +296,7 @@ int merge_keys(struct openpgp_publickey *a, struct openpgp_publickey *b)
 		 */
 		merge_signed_packets(&a->uids, &a->last_uid, 
 				&b->uids, &b->last_uid);
-		merge_signed_packets(&a->subkeys, &a->last_uid,
+		merge_signed_packets(&a->subkeys, &a->last_subkey,
 				&b->subkeys, &b->last_subkey);
 
 	}
@@ -301,6 +307,7 @@ int merge_keys(struct openpgp_publickey *a, struct openpgp_publickey *b)
 /**
  *	update_keys - Takes a list of public keys and updates them in the DB.
  *	@keys: The keys to update in the DB.
+ *	@verbose: Should we output more information as we add keys?
  *
  *	Takes a list of keys and adds them to the database, merging them with
  *	the key in the database if it's already present there. The key list is
@@ -308,15 +315,23 @@ int merge_keys(struct openpgp_publickey *a, struct openpgp_publickey *b)
  *	we had before to what we have now (ie the set of data that was added to
  *	the DB). Returns the number of entirely new keys added.
  */
-int update_keys(struct openpgp_publickey **keys)
+int update_keys(struct openpgp_publickey **keys, bool verbose)
 {
 	struct openpgp_publickey *curkey = NULL;
 	struct openpgp_publickey *oldkey = NULL;
-	struct	openpgp_publickey *prev = NULL;
+	struct openpgp_publickey *prev = NULL;
 	int newkeys = 0;
+	bool intrans;
 
 	for (curkey = *keys; curkey != NULL; curkey = curkey->next) {
-		fetch_key(get_keyid(curkey), &oldkey);
+		intrans = starttrans();
+		if (verbose) {
+			fprintf(stderr, "Fetching key 0x%llX, result: %d\n",
+				get_keyid(curkey),
+				fetch_key(get_keyid(curkey), &oldkey, intrans));
+		} else {
+			fetch_key(get_keyid(curkey), &oldkey, intrans);
+		}
 
 		/*
 		 * If we already have the key stored in the DB then merge it
@@ -337,14 +352,22 @@ int update_keys(struct openpgp_publickey **keys)
 				}
 			} else {
 				prev = curkey;
-				store_key(oldkey);
+				if (verbose) {
+					fprintf(stderr, "Merged key; storing updated key.\n");
+				}
+				store_key(oldkey, intrans, true);
 			}
 			free_publickey(oldkey);
 			oldkey = NULL;
 		} else {
-			store_key(curkey);
+			if (verbose) {
+				fprintf(stderr, "Storing completely new key.\n");
+			}
+			store_key(curkey, intrans, false);
 			newkeys++;
 		}
+		endtrans();
+		intrans = false;
 	}
 
 	return newkeys;
diff --git a/merge.h b/merge.h
index 208c39b..f298172 100644
--- a/merge.h
+++ b/merge.h
@@ -28,6 +28,7 @@ int merge_keys(struct openpgp_publickey *a, struct openpgp_publickey *b);
 /**
  *	update_keys - Takes a list of public keys and updates them in the DB.
  *	@keys: The keys to update in the DB.
+ *	@verbose: Should we output more information as we add keys?
  *
  *	Takes a list of keys and adds them to the database, merging them with
  *	the key in the database if it's already present there. The key list is
@@ -35,6 +36,6 @@ int merge_keys(struct openpgp_publickey *a, struct openpgp_publickey *b);
  *	we had before to what we have now (ie the set of data that was added to
  *	the DB). Returns the number of entirely new keys added.
  */
-int update_keys(struct openpgp_publickey **keys);
+int update_keys(struct openpgp_publickey **keys, bool verbose);
 
 #endif
diff --git a/onak_conf.c b/onak-conf.c
similarity index 74%
rename from onak_conf.c
rename to onak-conf.c
index a72d31d..a3ee64b 100644
--- a/onak_conf.c
+++ b/onak-conf.c
@@ -1,5 +1,5 @@
 /*
- * onak_conf.c - Routines related to runtime config.
+ * onak-conf.c - Routines related to runtime config.
  *
  * Jonathan McDowell 
  *
@@ -8,7 +8,7 @@
 
 #include 
 
-#include "onak_conf.h"
+#include "onak-conf.h"
 
 /*
  *	config - Runtime configuration for onak.
@@ -22,7 +22,7 @@ struct onak_config config = {
 	/*
 	 * Options for the db2 file backend.
 	 */
-	NULL,			/* db2_dbpath */
+	"/community/pgp-keyserver/db-copy",	/* db2_dbpath */
 
 	/*
 	 * Options for the file backend.
@@ -33,7 +33,7 @@ struct onak_config config = {
 	 * Options for the Postgres backend.
 	 */
 	NULL,			/* pg_dbhost */
-	NULL,			/* pg_dbname */
-	"noodles",		/* pg_dbuser */
+	"noodles",		/* pg_dbname */
+	NULL,			/* pg_dbuser */
 	NULL,			/* pg_dbpass */
 };
diff --git a/onak_conf.h b/onak-conf.h
similarity index 93%
rename from onak_conf.h
rename to onak-conf.h
index 9e11167..dde7320 100644
--- a/onak_conf.h
+++ b/onak-conf.h
@@ -1,5 +1,5 @@
 /*
- * onak_conf.h - Routines related to runtime config.
+ * onak-conf.h - Routines related to runtime config.
  *
  * Jonathan McDowell 
  *
@@ -9,7 +9,7 @@
 #ifndef __ONAK_CONF_H_
 #define __ONAK_CONF_H_
 
-#define VERSION "0.0.2"
+#define VERSION "0.0.3"
 
 /*
  *	struct onak_config - Runtime configuration for onak.
diff --git a/onak-mail.pl b/onak-mail.pl
index 30e5b93..0f108f4 100755
--- a/onak-mail.pl
+++ b/onak-mail.pl
@@ -22,14 +22,16 @@ sub submitupdate {
 	my @data = @_;
 	my (@errors, @mergedata);
 
-	open3(\*MERGEIN, \*MERGEOUT, \*MERGEERR, "/home/noodles/onak-0.0.2/keymerge");
+	open3(\*MERGEIN, \*MERGEOUT, \*MERGEERR, "/home/noodles/onak-0.0.3/onak", "add");
 
 	print MERGEIN @data;
 	close MERGEIN;
 	@errors = ;
 	@mergedata = ;
 
-	#print @errors;
+	open (LOG, ">>/home/noodles/onak-0.0.3/keyadd.log");
+	print LOG @errors;
+	close LOG;
 
 	return @mergedata;
 }
diff --git a/onak.c b/onak.c
index 7b0f2f3..e7c7486 100644
--- a/onak.c
+++ b/onak.c
@@ -10,13 +10,17 @@
 
 #include 
 #include 
+#include 
+#include 
 
 #include "armor.h"
 #include "keydb.h"
 #include "keyid.h"
+#include "keyindex.h"
 #include "keystructs.h"
 #include "mem.h"
 #include "merge.h"
+#include "onak-conf.h"
 #include "parsekey.h"
 
 int stdin_getchar(void *ctx, size_t count, unsigned char *c)
@@ -31,11 +35,50 @@ int stdin_getchar(void *ctx, size_t count, unsigned char *c)
 	return (ic == EOF);
 }
 
-int stdout_putchar(void *ctx, unsigned char c)
+int stdout_putchar(void *ctx, size_t count, unsigned char *c)
 {
-	return (putchar(c));
+	int i;
+
+	for (i = 0; i < count; i++) {
+		putchar(c[i]);
+	}
+	return 0;
 }
 
+void find_keys(char *search, uint64_t keyid, bool ishex,
+		bool fingerprint, bool exact, bool verbose)
+{
+	struct openpgp_publickey *publickey = NULL;
+	int count = 0;
+
+	if (ishex) {
+		count = fetch_key(keyid, &publickey, false);
+	} else {
+		count = fetch_key_text(search, &publickey);
+	}
+	if (publickey != NULL) {
+		key_index(publickey, verbose, fingerprint, false);
+		free_publickey(publickey);
+	} else if (count == 0) {
+		puts("Key not found.");
+	} else {
+		printf("Found %d keys, but maximum number to return is %d.\n",
+				count,
+				config.maxkeys);
+		puts("Try again with a more specific search.");
+	}
+}
+
+void usage(void) {
+	puts("onak " VERSION " - an OpenPGP keyserver.\n");
+	puts("Usage:\n");
+	puts("\tonak [options]  \n");
+	puts("\tCommands:\n");
+	puts("\tadd    - read armored OpenPGP keys from stdin and add to the keyserver");
+	puts("\tdelete - delete a given key from the keyserver");
+	puts("\tindex  - search for a key and list it");
+	puts("\tvindex - search for a key and list it and its signatures");
+}
 
 int main(int argc, char *argv[])
 {
@@ -43,28 +86,93 @@ int main(int argc, char *argv[])
 	struct openpgp_packet_list	*list_end = NULL;
 	struct openpgp_publickey	*keys = NULL;
 	int				 rc = EXIT_SUCCESS;
+	char				*search = NULL;
+	char				*end = NULL;
+	uint64_t			 keyid = 0;
+	bool				 ishex = false;
+	bool				 verbose = false;
+	bool				 binary = false;
+	int				 optchar;
 
-	read_openpgp_stream(stdin_getchar, NULL, &packets);
-	if (packets != NULL) {
-		parse_keys(packets, &keys);
-		free_packet_list(packets);
-		packets = NULL;
 
-		initdb();
-		fprintf(stderr, "Got %d new keys.\n",
-				update_keys(&keys));
-		cleanupdb();
-	} else {
-		rc = 1;
-		fprintf(stderr, "No keys read.\n");
+	while ((optchar = getopt(argc, argv, "bv")) != -1 ) {
+		switch (optchar) {
+		case 'b': 
+			binary = true;
+			break;
+		case 'v': 
+			verbose = true;
+			break;
+		}
 	}
 
-	if (keys != NULL) {
-		free_publickey(keys);
-		keys = NULL;
+	if ((argc - optind) < 1) {
+		usage();
+	} else if (!strcmp("add", argv[optind])) {
+		if (binary) {
+			read_openpgp_stream(stdin_getchar, NULL, &packets);
+		} else {
+			dearmor_openpgp_stream(stdin_getchar, NULL, &packets);
+		}
+		if (packets != NULL) {
+			parse_keys(packets, &keys);
+			free_packet_list(packets);
+			packets = NULL;
+			if (verbose) {
+				fprintf(stderr, "Finished reading keys.\n");
+			}
+	
+			initdb();
+			fprintf(stderr, "Got %d new keys.\n",
+					update_keys(&keys, verbose));
+			cleanupdb();
+		} else {
+			rc = 1;
+			fprintf(stderr, "No keys read.\n");
+		}
+
+		if (keys != NULL) {
+			free_publickey(keys);
+			keys = NULL;
+		} else {
+			rc = 1;
+			fprintf(stderr, "No changes.\n");
+		}
+	} else if ((argc - optind) == 2) {
+		search = argv[optind+1];
+		if (search != NULL) {
+			keyid = strtoul(search, &end, 16);
+			if (*search != 0 &&
+					end != NULL &&
+					*end == 0) {
+				ishex = true;
+			}
+		}
+		initdb();
+		if (!strcmp("index", argv[optind])) {
+			find_keys(search, keyid, ishex, false, false, false);
+		} else if (!strcmp("vindex", argv[optind])) {
+			find_keys(search, keyid, ishex, false, false, true);
+		} else if (!strcmp("delete", argv[optind])) {
+			delete_key(getfullkeyid(keyid), false);
+		} else if (!strcmp("get", argv[optind])) {
+			if (fetch_key(keyid, &keys, false)) {
+				if (verbose) {
+					fprintf(stderr, "Got key.\n");
+				}
+				flatten_publickey(keys,
+						&packets,
+						&list_end);
+				armor_openpgp_stream(stdout_putchar,
+						NULL,
+						packets);
+			} else {
+				puts("Key not found");
+			}
+		}
+		cleanupdb();
 	} else {
-		rc = 1;
-		fprintf(stderr, "No changes.\n");
+		usage();
 	}
 
 	return rc;
diff --git a/onak.gprof b/onak.gprof
new file mode 100644
index 0000000..0e8b40b
--- /dev/null
+++ b/onak.gprof
@@ -0,0 +1,295 @@
+Flat profile:
+
+Each sample counts as 0.01 seconds.
+  %   cumulative   self              self     total           
+ time   seconds   seconds    calls  us/call  us/call  name    
+ 44.57      0.41     0.41    44208     9.27    14.02  transform
+ 22.83      0.62     0.21  9902592     0.02     0.02  rol
+  9.78      0.71     0.09    31610     2.85    19.66  sha1_write
+  7.61      0.78     0.07    33623     2.08     2.08  stdin_getchar
+  2.17      0.80     0.02     6937     2.88   106.67  get_keyid
+  2.17      0.82     0.02     1690    11.83    17.75  write_openpgp_stream
+  2.17      0.84     0.02                             Letext
+  1.09      0.85     0.01    30179     0.33     0.33  keydb_putchar
+  1.09      0.86     0.01    17848     0.56     0.56  packet_dup
+  1.09      0.87     0.01     6322     1.58    35.26  sha1_final
+  1.09      0.88     0.01     1690     5.92     8.88  flatten_publickey
+  1.09      0.89     0.01     1690     5.92     5.92  keyuids
+  1.09      0.90     0.01     1690     5.92   262.98  store_key
+  1.09      0.91     0.01        1 10000.00 10000.00  free_publickey
+  1.09      0.92     0.01        1 10000.00 815000.00  update_keys
+  0.00      0.92     0.00    17848     0.00     0.00  free_packet
+  0.00      0.92     0.00     6322     0.00     0.00  sha1_init
+  0.00      0.92     0.00     6322     0.00     0.00  sha1_read
+  0.00      0.92     0.00     3438     0.00     0.00  free_packet_list
+  0.00      0.92     0.00     3227     0.00     0.00  free_signedpacket_list
+  0.00      0.92     0.00     1690     0.00     0.00  endtrans
+  0.00      0.92     0.00     1690     0.00     0.00  fetch_key
+  0.00      0.92     0.00     1690     0.00     0.00  spsize
+  0.00      0.92     0.00     1690     0.00     0.00  starttrans
+  0.00      0.92     0.00        1     0.00     0.00  cleanupdb
+  0.00      0.92     0.00        1     0.00     0.00  initdb
+  0.00      0.92     0.00        1     0.00  5000.00  parse_keys
+  0.00      0.92     0.00        1     0.00 70000.00  read_openpgp_stream
+
+ %         the percentage of the total running time of the
+time       program used by this function.
+
+cumulative a running sum of the number of seconds accounted
+ seconds   for by this function and those listed above it.
+
+ self      the number of seconds accounted for by this
+seconds    function alone.  This is the major sort for this
+           listing.
+
+calls      the number of times this function was invoked, if
+           this function is profiled, else blank.
+ 
+ self      the average number of milliseconds spent in this
+ms/call    function per call, if this function is profiled,
+	   else blank.
+
+ total     the average number of milliseconds spent in this
+ms/call    function and its descendents per call, if this 
+	   function is profiled, else blank.
+
+name       the name of the function.  This is the minor sort
+           for this listing. The index shows the location of
+	   the function in the gprof listing. If the index is
+	   in parenthesis it shows where it would appear in
+	   the gprof listing if it were to be printed.
+
+		     Call graph (explanation follows)
+
+
+granularity: each sample hit covers 4 byte(s) for 1.09% of 0.92 seconds
+
+index % time    self  children    called     name
+                                                 
+[1]     97.8    0.00    0.90                 main [1]
+                0.01    0.81       1/1           update_keys [2]
+                0.00    0.07       1/1           read_openpgp_stream [10]
+                0.01    0.00       1/1           free_publickey [17]
+                0.00    0.01       1/1           parse_keys [18]
+                0.00    0.00       1/3438        free_packet_list [22]
+                0.00    0.00       1/1           initdb [29]
+                0.00    0.00       1/1           cleanupdb [28]
+-----------------------------------------------
+                0.01    0.81       1/1           main [1]
+[2]     88.6    0.01    0.81       1         update_keys [2]
+                0.01    0.43    1690/1690        store_key [6]
+                0.01    0.35    3380/6937        get_keyid [3]
+                0.00    0.00    1690/1690        starttrans [27]
+                0.00    0.00    1690/1690        fetch_key [25]
+                0.00    0.00    1690/1690        endtrans [24]
+-----------------------------------------------
+                0.01    0.35    3380/6937        update_keys [2]
+                0.01    0.37    3557/6937        store_key [6]
+[3]     80.4    0.02    0.72    6937         get_keyid [3]
+                0.07    0.43   25288/31610       sha1_write [4]
+                0.01    0.21    6322/6322        sha1_final [7]
+                0.00    0.00    6322/6322        sha1_init [20]
+                0.00    0.00    6322/6322        sha1_read [21]
+-----------------------------------------------
+                               18966             sha1_write [4]
+                0.02    0.11    6322/31610       sha1_final [7]
+                0.07    0.43   25288/31610       get_keyid [3]
+[4]     67.5    0.09    0.53   31610+18966   sha1_write [4]
+                0.35    0.18   37886/44208       transform [5]
+                               18966             sha1_write [4]
+-----------------------------------------------
+                0.06    0.03    6322/44208       sha1_final [7]
+                0.35    0.18   37886/44208       sha1_write [4]
+[5]     67.4    0.41    0.21   44208         transform [5]
+                0.21    0.00 9902592/9902592     rol [8]
+-----------------------------------------------
+                0.01    0.43    1690/1690        update_keys [2]
+[6]     48.3    0.01    0.43    1690         store_key [6]
+                0.01    0.37    3557/6937        get_keyid [3]
+                0.02    0.01    1690/1690        write_openpgp_stream [11]
+                0.01    0.01    1690/1690        flatten_publickey [13]
+                0.01    0.00    1690/1690        keyuids [16]
+-----------------------------------------------
+                0.01    0.21    6322/6322        get_keyid [3]
+[7]     24.2    0.01    0.21    6322         sha1_final [7]
+                0.02    0.11    6322/31610       sha1_write [4]
+                0.06    0.03    6322/44208       transform [5]
+-----------------------------------------------
+                0.21    0.00 9902592/9902592     transform [5]
+[8]     22.8    0.21    0.00 9902592         rol [8]
+-----------------------------------------------
+                0.07    0.00   33623/33623       read_openpgp_stream [10]
+[9]      7.6    0.07    0.00   33623         stdin_getchar [9]
+-----------------------------------------------
+                0.00    0.07       1/1           main [1]
+[10]     7.6    0.00    0.07       1         read_openpgp_stream [10]
+                0.07    0.00   33623/33623       stdin_getchar [9]
+-----------------------------------------------
+                0.02    0.01    1690/1690        store_key [6]
+[11]     3.3    0.02    0.01    1690         write_openpgp_stream [11]
+                0.01    0.00   30179/30179       keydb_putchar [14]
+-----------------------------------------------
+                                                 
+[12]     2.2    0.02    0.00                 Letext [12]
+-----------------------------------------------
+                0.01    0.01    1690/1690        store_key [6]
+[13]     1.6    0.01    0.01    1690         flatten_publickey [13]
+                0.01    0.00    8924/17848       packet_dup [15]
+-----------------------------------------------
+                0.01    0.00   30179/30179       write_openpgp_stream [11]
+[14]     1.1    0.01    0.00   30179         keydb_putchar [14]
+-----------------------------------------------
+                0.01    0.00    8924/17848       parse_keys [18]
+                0.01    0.00    8924/17848       flatten_publickey [13]
+[15]     1.1    0.01    0.00   17848         packet_dup [15]
+-----------------------------------------------
+                0.01    0.00    1690/1690        store_key [6]
+[16]     1.1    0.01    0.00    1690         keyuids [16]
+                0.00    0.00    1690/1690        spsize [26]
+-----------------------------------------------
+                0.01    0.00       1/1           main [1]
+[17]     1.1    0.01    0.00       1         free_publickey [17]
+                0.00    0.00    3227/3227        free_signedpacket_list [23]
+                0.00    0.00    1690/17848       free_packet [19]
+                0.00    0.00      41/3438        free_packet_list [22]
+-----------------------------------------------
+                0.00    0.01       1/1           main [1]
+[18]     0.5    0.00    0.01       1         parse_keys [18]
+                0.01    0.00    8924/17848       packet_dup [15]
+-----------------------------------------------
+                0.00    0.00    1690/17848       free_publickey [17]
+                0.00    0.00    3410/17848       free_signedpacket_list [23]
+                0.00    0.00   12748/17848       free_packet_list [22]
+[19]     0.0    0.00    0.00   17848         free_packet [19]
+-----------------------------------------------
+                0.00    0.00    6322/6322        get_keyid [3]
+[20]     0.0    0.00    0.00    6322         sha1_init [20]
+-----------------------------------------------
+                0.00    0.00    6322/6322        get_keyid [3]
+[21]     0.0    0.00    0.00    6322         sha1_read [21]
+-----------------------------------------------
+                0.00    0.00       1/3438        main [1]
+                0.00    0.00      41/3438        free_publickey [17]
+                0.00    0.00    3396/3438        free_signedpacket_list [23]
+[22]     0.0    0.00    0.00    3438         free_packet_list [22]
+                0.00    0.00   12748/17848       free_packet [19]
+-----------------------------------------------
+                0.00    0.00    3227/3227        free_publickey [17]
+[23]     0.0    0.00    0.00    3227         free_signedpacket_list [23]
+                0.00    0.00    3410/17848       free_packet [19]
+                0.00    0.00    3396/3438        free_packet_list [22]
+-----------------------------------------------
+                0.00    0.00    1690/1690        update_keys [2]
+[24]     0.0    0.00    0.00    1690         endtrans [24]
+-----------------------------------------------
+                0.00    0.00    1690/1690        update_keys [2]
+[25]     0.0    0.00    0.00    1690         fetch_key [25]
+-----------------------------------------------
+                0.00    0.00    1690/1690        keyuids [16]
+[26]     0.0    0.00    0.00    1690         spsize [26]
+-----------------------------------------------
+                0.00    0.00    1690/1690        update_keys [2]
+[27]     0.0    0.00    0.00    1690         starttrans [27]
+-----------------------------------------------
+                0.00    0.00       1/1           main [1]
+[28]     0.0    0.00    0.00       1         cleanupdb [28]
+-----------------------------------------------
+                0.00    0.00       1/1           main [1]
+[29]     0.0    0.00    0.00       1         initdb [29]
+-----------------------------------------------
+
+ This table describes the call tree of the program, and was sorted by
+ the total amount of time spent in each function and its children.
+
+ Each entry in this table consists of several lines.  The line with the
+ index number at the left hand margin lists the current function.
+ The lines above it list the functions that called this function,
+ and the lines below it list the functions this one called.
+ This line lists:
+     index	A unique number given to each element of the table.
+		Index numbers are sorted numerically.
+		The index number is printed next to every function name so
+		it is easier to look up where the function in the table.
+
+     % time	This is the percentage of the `total' time that was spent
+		in this function and its children.  Note that due to
+		different viewpoints, functions excluded by options, etc,
+		these numbers will NOT add up to 100%.
+
+     self	This is the total amount of time spent in this function.
+
+     children	This is the total amount of time propagated into this
+		function by its children.
+
+     called	This is the number of times the function was called.
+		If the function called itself recursively, the number
+		only includes non-recursive calls, and is followed by
+		a `+' and the number of recursive calls.
+
+     name	The name of the current function.  The index number is
+		printed after it.  If the function is a member of a
+		cycle, the cycle number is printed between the
+		function's name and the index number.
+
+
+ For the function's parents, the fields have the following meanings:
+
+     self	This is the amount of time that was propagated directly
+		from the function into this parent.
+
+     children	This is the amount of time that was propagated from
+		the function's children into this parent.
+
+     called	This is the number of times this parent called the
+		function `/' the total number of times the function
+		was called.  Recursive calls to the function are not
+		included in the number after the `/'.
+
+     name	This is the name of the parent.  The parent's index
+		number is printed after it.  If the parent is a
+		member of a cycle, the cycle number is printed between
+		the name and the index number.
+
+ If the parents of the function cannot be determined, the word
+ `' is printed in the `name' field, and all the other
+ fields are blank.
+
+ For the function's children, the fields have the following meanings:
+
+     self	This is the amount of time that was propagated directly
+		from the child into the function.
+
+     children	This is the amount of time that was propagated from the
+		child's children to the function.
+
+     called	This is the number of times the function called
+		this child `/' the total number of times the child
+		was called.  Recursive calls by the child are not
+		listed in the number after the `/'.
+
+     name	This is the name of the child.  The child's index
+		number is printed after it.  If the child is a
+		member of a cycle, the cycle number is printed
+		between the name and the index number.
+
+ If there are any cycles (circles) in the call graph, there is an
+ entry for the cycle-as-a-whole.  This entry shows who called the
+ cycle (as parents) and the members of the cycle (as children.)
+ The `+' recursive calls entry shows the number of function calls that
+ were internal to the cycle, and the calls entry for each member shows,
+ for that member, how many times it was called from other members of
+ the cycle.
+
+
+Index by function name
+
+  [12] Letext (bithelp.h)     [29] initdb                  [4] sha1_write
+  [28] cleanupdb              [14] keydb_putchar (keydb_pg.c) [26] spsize
+  [24] endtrans               [16] keyuids                [27] starttrans
+  [25] fetch_key              [15] packet_dup              [9] stdin_getchar
+  [13] flatten_publickey      [18] parse_keys              [6] store_key
+  [19] free_packet            [10] read_openpgp_stream     [5] transform (sha.c)
+  [22] free_packet_list        [8] rol (bithelp.h)         [2] update_keys
+  [17] free_publickey          [7] sha1_final             [11] write_openpgp_stream
+  [23] free_signedpacket_list [20] sha1_init
+   [3] get_keyid              [21] sha1_read
diff --git a/onak.sql b/onak.sql
index 6afa737..9fc033c 100644
--- a/onak.sql
+++ b/onak.sql
@@ -6,6 +6,7 @@ CREATE TABLE onak_keys (
 	keyid	char(16) NOT NULL,
 	keydata	oid NOT NULL
 );
+CREATE INDEX onak_keys_keyid_index ON onak_keys(keyid);
 
 CREATE TABLE onak_uids (
 	keyid	char(16) NOT NULL,
diff --git a/parsekey.c b/parsekey.c
index f83e805..fbdde15 100644
--- a/parsekey.c
+++ b/parsekey.c
@@ -281,64 +281,66 @@ int read_openpgp_stream(int (*getchar_func)(void *ctx, size_t count,
  *	This function uses putchar_func to write characters to an OpenPGP
  *	packet stream from a linked list of packets.
  */
-int write_openpgp_stream(int (*putchar_func)(void *ctx, unsigned char c),
+int write_openpgp_stream(int (*putchar_func)(void *ctx, size_t count,
+						unsigned char *c),
 				void *ctx,
 				struct openpgp_packet_list *packets)
 {
 	unsigned char	curchar = 0;
-	int		i;
 
 	while (packets != NULL) {
 		curchar = 0x80;
 		if (packets->packet->newformat) {
 			curchar |= 0x40;
 			curchar |= packets->packet->tag;
-			putchar_func(ctx, curchar);
+			putchar_func(ctx, 1, &curchar);
 
 			if (packets->packet->length < 192) {
-				putchar_func(ctx, packets->packet->length);
+				curchar = packets->packet->length;
+				putchar_func(ctx, 1, &curchar);
 			} else if (packets->packet->length > 191 &&
 				packets->packet->length < 8383) {
-//				fputs("Potentially dodgy code here.\n", stderr);
-				putchar_func(ctx, 
-					(((packets->packet->length - 192) &
-					 0xFF00) >> 8) + 192);
-
-				putchar_func(ctx, 
-					(packets->packet->length - 192) &
-					 0xFF);
+				curchar = (((packets->packet->length - 192) &
+					 0xFF00) >> 8) + 192;
+				putchar_func(ctx, 1, &curchar);
 
+				curchar = (packets->packet->length - 192) &
+					 0xFF;
+				putchar_func(ctx, 1, &curchar);
 			} else {
 				fputs("Unsupported new format length.\n", stderr);
 			}
 		} else {
 			curchar |= (packets->packet->tag << 2);
 			if (packets->packet->length < 256) {
-				putchar_func(ctx, curchar);
-				putchar_func(ctx, packets->packet->length);
+				putchar_func(ctx, 1, &curchar);
+				curchar = packets->packet->length;
+				putchar_func(ctx, 1, &curchar);
 			} else if (packets->packet->length < 0x10000) {
 				curchar |= 1;
-				putchar_func(ctx, curchar);
-				putchar_func(ctx, packets->packet->length >> 8);
-				putchar_func(ctx,
-					packets->packet->length & 0xFF);
+				putchar_func(ctx, 1, &curchar);
+				curchar = packets->packet->length >> 8;
+				putchar_func(ctx, 1, &curchar);
+				curchar = packets->packet->length & 0xFF;
+				putchar_func(ctx, 1, &curchar);
 			} else {
 				curchar |= 2;
-				putchar_func(ctx, curchar);
-				putchar_func(ctx,
-					packets->packet->length >> 24);
-				putchar_func(ctx,
-					(packets->packet->length >> 16) & 0xFF);
-				putchar_func(ctx,
-					(packets->packet->length >> 8) & 0xFF);
-				putchar_func(ctx,
-					packets->packet->length & 0xFF);
+				putchar_func(ctx, 1, &curchar);
+				curchar = packets->packet->length >> 24;
+				putchar_func(ctx, 1, &curchar);
+				curchar = (packets->packet->length >> 16) & 0xFF;
+				putchar_func(ctx, 1, &curchar);
+				curchar = (packets->packet->length >> 8) & 0xFF;
+				putchar_func(ctx, 1, &curchar);
+				curchar = packets->packet->length & 0xFF;
+				putchar_func(ctx, 1, &curchar);
 			}
 		}
 
-		for (i = 0; i < packets->packet->length; i++) {
-			putchar_func(ctx, packets->packet->data[i]);
-		}
+		putchar_func(ctx, packets->packet->length, packets->packet->data);
+//		for (i = 0; i < packets->packet->length; i++) {
+//			putchar_func(ctx, packets->packet->data[i]);
+//		}
 		packets = packets->next;
 	}
 	return 0;
diff --git a/parsekey.h b/parsekey.h
index 113f14a..2bfaf72 100644
--- a/parsekey.h
+++ b/parsekey.h
@@ -68,7 +68,8 @@ int read_openpgp_stream(int (*getchar_func)(void *ctx, size_t count,
  *	This function uses putchar_func to write characters to an OpenPGP
  *	packet stream from a linked list of packets.
  */
-int write_openpgp_stream(int (*putchar_func)(void *ctx, unsigned char c),
+int write_openpgp_stream(int (*putchar_func)(void *ctx, size_t count,
+						unsigned char *c),
 				void *ctx,
 				struct openpgp_packet_list *packets);
 
diff --git a/pathtest.c b/pathtest.c
deleted file mode 100644
index 50dcdcf..0000000
--- a/pathtest.c
+++ /dev/null
@@ -1,65 +0,0 @@
-//#include 
-#include 
-#include 
-#include 
-
-#include "hash.h"
-#include "keydb.h"
-#include "stats.h"
-
-void dofindpath(uint64_t have, uint64_t want, bool html)
-{
-	struct stats_key *keyinfoa, *keyinfob, *curkey;
-	int rec;
-
-	/*
-	 * Make sure the key we have and want are in the cache.
-	 */
-	hash_getkeysigs(have);
-	hash_getkeysigs(want);
-
-	if ((keyinfoa = findinhash(have)) == NULL) {
-		printf("550 Couldn't find key 0x%llX.\n", have);
-		return;
-	}
-	if ((keyinfob = findinhash(want)) == NULL) {
-		printf("550 Couldn't find key 0x%llX.\n", want);
-		return;
-	}
-	
-	/*
-	 * Fill the tree info up.
-	 */
-	initcolour(true);
-	rec = findpath(keyinfoa, keyinfob);
-	keyinfob->parent = 0;
-
-	printf("%d nodes examined. %ld elements in the hash\n", rec,
-			hashelements());
-	if (keyinfoa->colour == 0) {
-		printf("550 Can't find a link from 0x%llX to 0x%llX\n",
-				have,
-				want);
-	} else {
-		printf("250-%d steps from 0x%llX to 0x%llX\n",
-				keyinfoa->colour, have, want);
-		curkey = keyinfoa;
-		while (curkey != NULL) {
-			printf("250-0x%llX (%s)\n",
-					curkey->keyid,
-					keyid2uid(curkey->keyid));
-			curkey = findinhash(curkey->parent);
-		}
-	}
-}
-
-int main(int argc, char *argv[])
-{
-	initdb();
-	inithash();
-	dofindpath(0x5B430367, 0x3E1D0C1C, false);
-	dofindpath(0x3E1D0C1C, 0x5B430367, false);
-	cleanupdb();
-
-	return EXIT_SUCCESS;
-}
diff --git a/stats.c b/stats.c
index 0e2662b..b302f09 100644
--- a/stats.c
+++ b/stats.c
@@ -6,6 +6,7 @@
  * Copyright 2000-2002 Project Purple
  */
 
+#include 
 #include 
 
 #include "hash.h"
@@ -87,6 +88,7 @@ unsigned long findpath(struct stats_key *have, struct stats_key *want)
 			nextkeys = NULL;
 			curdegree++;
 		}
+		fprintf(stderr, "Hash contains %ld keys.\n", hashelements());
 	}
 
 	return count;