2 * keydb_pg.c - Routines to store and fetch keys in a PostGres database.
4 * Jonathan McDowell <noodles@earth.li>
6 * Copyright 2002-2004 Project Purple
9 #include <postgresql/libpq-fe.h>
10 #include <postgresql/libpq/libpq-fs.h>
12 #include <sys/types.h>
24 #include "decodekey.h"
25 #include "keystructs.h"
28 #include "onak-conf.h"
32 * dbconn - our connection to the database.
34 static PGconn *dbconn = NULL;
37 * keydb_fetchchar - Fetches a char from a file.
39 static int keydb_fetchchar(void *fd, size_t count, unsigned char *c)
41 return (!lo_read(dbconn, *(int *) fd, (char *) c, count));
45 * keydb_putchar - Puts a char to a file.
47 static int keydb_putchar(void *fd, size_t count, unsigned char *c)
49 return !(lo_write(dbconn, *(int *) fd, (char *) c, count));
53 * initdb - Initialize the key database.
55 * This function should be called before any of the other functions in
56 * this file are called in order to allow the DB to be initialized ready
59 static void pg_initdb(bool readonly)
61 dbconn = PQsetdbLogin(config.pg_dbhost, // host
65 config.pg_dbname, // database
66 config.pg_dbuser, //login
67 config.pg_dbpass); // password
69 if (PQstatus(dbconn) == CONNECTION_BAD) {
70 logthing(LOGTHING_CRITICAL, "Connection to database failed.");
71 logthing(LOGTHING_CRITICAL, "%s", PQerrorMessage(dbconn));
79 * cleanupdb - De-initialize the key database.
81 * This function should be called upon program exit to allow the DB to
82 * cleanup after itself.
84 static void pg_cleanupdb(void)
91 * starttrans - Start a transaction.
93 * Start a transaction. Intended to be used if we're about to perform many
94 * operations on the database to help speed it all up, or if we want
95 * something to only succeed if all relevant operations are successful.
97 static bool pg_starttrans(void)
99 PGresult *result = NULL;
101 result = PQexec(dbconn, "BEGIN");
108 * endtrans - End a transaction.
110 * Ends a transaction.
112 static void pg_endtrans(void)
114 PGresult *result = NULL;
116 result = PQexec(dbconn, "COMMIT");
123 * fetch_key - Given a keyid fetch the key from storage.
124 * @keyid: The keyid to fetch.
125 * @publickey: A pointer to a structure to return the key in.
126 * @intrans: If we're already in a transaction.
128 * We use the hex representation of the keyid as the filename to fetch the
129 * key from. The key is stored in the file as a binary OpenPGP stream of
130 * packets, so we can just use read_openpgp_stream() to read the packets
131 * in and then parse_keys() to parse the packets into a publickey
134 static int pg_fetch_key(uint64_t keyid, struct openpgp_publickey **publickey,
137 struct openpgp_packet_list *packets = NULL;
138 PGresult *result = NULL;
140 char statement[1024];
147 result = PQexec(dbconn, "BEGIN");
151 if (keyid > 0xFFFFFFFF) {
152 snprintf(statement, 1023,
153 "SELECT keydata FROM onak_keys WHERE keyid = '%"
157 snprintf(statement, 1023,
158 "SELECT keydata FROM onak_keys WHERE keyid "
159 "LIKE '%%%" PRIX64 "'",
162 result = PQexec(dbconn, statement);
164 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
165 numkeys = PQntuples(result);
166 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
167 oids = PQgetvalue(result, i, 0);
168 key_oid = (Oid) atoi(oids);
170 fd = lo_open(dbconn, key_oid, INV_READ);
172 logthing(LOGTHING_ERROR,
173 "Can't open large object.");
175 read_openpgp_stream(keydb_fetchchar, &fd,
177 parse_keys(packets, publickey);
178 lo_close(dbconn, fd);
179 free_packet_list(packets);
183 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
184 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
190 result = PQexec(dbconn, "COMMIT");
197 * fetch_key_text - Trys to find the keys that contain the supplied text.
198 * @search: The text to search for.
199 * @publickey: A pointer to a structure to return the key in.
201 * This function searches for the supplied text and returns the keys that
204 static int pg_fetch_key_text(const char *search,
205 struct openpgp_publickey **publickey)
207 struct openpgp_packet_list *packets = NULL;
208 PGresult *result = NULL;
210 char statement[1024];
215 char *newsearch = NULL;
217 result = PQexec(dbconn, "BEGIN");
220 newsearch = malloc(strlen(search) * 2 + 1);
221 memset(newsearch, 0, strlen(search) * 2 + 1);
222 PQescapeStringConn(dbconn, newsearch, search, strlen(search), NULL);
223 snprintf(statement, 1023,
224 "SELECT DISTINCT onak_keys.keydata FROM onak_keys, "
225 "onak_uids WHERE onak_keys.keyid = onak_uids.keyid "
226 "AND onak_uids.uid LIKE '%%%s%%'",
228 result = PQexec(dbconn, statement);
232 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
233 numkeys = PQntuples(result);
234 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
235 oids = PQgetvalue(result, i, 0);
236 key_oid = (Oid) atoi(oids);
238 fd = lo_open(dbconn, key_oid, INV_READ);
240 logthing(LOGTHING_ERROR,
241 "Can't open large object.");
243 read_openpgp_stream(keydb_fetchchar, &fd,
246 parse_keys(packets, publickey);
247 lo_close(dbconn, fd);
248 free_packet_list(packets);
252 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
253 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
258 result = PQexec(dbconn, "COMMIT");
264 * delete_key - Given a keyid delete the key from storage.
265 * @keyid: The keyid to delete.
266 * @intrans: If we're already in a transaction.
268 * This function deletes a public key from whatever storage mechanism we
269 * are using. Returns 0 if the key existed.
271 static int pg_delete_key(uint64_t keyid, bool intrans)
273 PGresult *result = NULL;
275 char statement[1024];
281 result = PQexec(dbconn, "BEGIN");
285 snprintf(statement, 1023,
286 "SELECT keydata FROM onak_keys WHERE keyid = '%"
289 result = PQexec(dbconn, statement);
291 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
293 i = PQntuples(result);
295 oids = PQgetvalue(result, i-1, 0);
296 key_oid = (Oid) atoi(oids);
297 lo_unlink(dbconn, key_oid);
302 snprintf(statement, 1023,
303 "DELETE FROM onak_keys WHERE keyid = '%" PRIX64 "'",
305 result = PQexec(dbconn, statement);
308 snprintf(statement, 1023,
309 "DELETE FROM onak_sigs WHERE signee = '%" PRIX64 "'",
311 result = PQexec(dbconn, statement);
314 snprintf(statement, 1023,
315 "DELETE FROM onak_uids WHERE keyid = '%" PRIX64 "'",
317 result = PQexec(dbconn, statement);
318 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
319 logthing(LOGTHING_ERROR,
320 "Problem retrieving key (%" PRIX64
328 result = PQexec(dbconn, "COMMIT");
335 * store_key - Takes a key and stores it.
336 * @publickey: A pointer to the public key to store.
337 * @intrans: If we're already in a transaction.
338 * @update: If true the key exists and should be updated.
340 * Again we just use the hex representation of the keyid as the filename
341 * to store the key to. We flatten the public key to a list of OpenPGP
342 * packets and then use write_openpgp_stream() to write the stream out to
343 * the file. If update is true then we delete the old key first, otherwise
344 * we trust that it doesn't exist.
346 static int pg_store_key(struct openpgp_publickey *publickey, bool intrans,
349 struct openpgp_packet_list *packets = NULL;
350 struct openpgp_packet_list *list_end = NULL;
351 struct openpgp_publickey *next = NULL;
352 struct openpgp_signedpacket_list *curuid = NULL;
353 PGresult *result = NULL;
354 char statement[1024];
358 char *primary = NULL;
359 char *safeuid = NULL;
363 result = PQexec(dbconn, "BEGIN");
368 * Delete the key if we already have it.
370 * TODO: Can we optimize this perhaps? Possibly when other data is
371 * involved as well? I suspect this is easiest and doesn't make a lot
372 * of difference though - the largest chunk of data is the keydata and
373 * it definitely needs updated.
376 pg_delete_key(get_keyid(publickey), true);
379 next = publickey->next;
380 publickey->next = NULL;
381 flatten_publickey(publickey, &packets, &list_end);
382 publickey->next = next;
384 key_oid = lo_creat(dbconn, INV_READ | INV_WRITE);
386 logthing(LOGTHING_ERROR, "Can't create key OID");
388 fd = lo_open(dbconn, key_oid, INV_WRITE);
389 write_openpgp_stream(keydb_putchar, &fd, packets);
390 lo_close(dbconn, fd);
392 free_packet_list(packets);
395 snprintf(statement, 1023,
396 "INSERT INTO onak_keys (keyid, keydata) VALUES "
397 "('%" PRIX64 "', '%d')",
398 get_keyid(publickey),
400 result = PQexec(dbconn, statement);
402 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
403 logthing(LOGTHING_ERROR, "Problem storing key in DB.");
404 logthing(LOGTHING_ERROR, "%s", PQresultErrorMessage(result));
408 uids = keyuids(publickey, &primary);
410 for (i = 0; uids[i] != NULL; i++) {
411 safeuid = malloc(strlen(uids[i]) * 2 + 1);
412 if (safeuid != NULL) {
413 memset(safeuid, 0, strlen(uids[i]) * 2 + 1);
414 PQescapeStringConn(dbconn, safeuid, uids[i],
415 strlen(uids[i]), NULL);
417 snprintf(statement, 1023,
418 "INSERT INTO onak_uids "
420 "VALUES ('%" PRIX64 "', '%s', '%c')",
421 get_keyid(publickey),
423 (uids[i] == primary) ? 't' : 'f');
424 result = PQexec(dbconn, statement);
429 if (uids[i] != NULL) {
434 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
435 logthing(LOGTHING_ERROR,
436 "Problem storing key in DB.");
437 logthing(LOGTHING_ERROR, "%s",
438 PQresultErrorMessage(result));
441 * TODO: Check result.
449 for (curuid = publickey->uids; curuid != NULL; curuid = curuid->next) {
450 for (packets = curuid->sigs; packets != NULL;
451 packets = packets->next) {
452 snprintf(statement, 1023,
453 "INSERT INTO onak_sigs (signer, signee) "
454 "VALUES ('%" PRIX64 "', '%" PRIX64 "')",
455 sig_keyid(packets->packet),
456 get_keyid(publickey));
457 result = PQexec(dbconn, statement);
463 result = PQexec(dbconn, "COMMIT");
471 * keyid2uid - Takes a keyid and returns the primary UID for it.
472 * @keyid: The keyid to lookup.
474 static char *pg_keyid2uid(uint64_t keyid)
476 PGresult *result = NULL;
477 char statement[1024];
480 snprintf(statement, 1023,
481 "SELECT uid FROM onak_uids WHERE keyid = '%" PRIX64
484 result = PQexec(dbconn, statement);
487 * Technically we only expect one response to the query; a key only has
488 * one primary ID. Better to return something than nothing though.
490 * TODO: Log if we get more than one response? Needs logging framework
493 if (PQresultStatus(result) == PGRES_TUPLES_OK &&
494 PQntuples(result) >= 1) {
495 uid = strdup(PQgetvalue(result, 0, 0));
496 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
497 logthing(LOGTHING_ERROR,
498 "Problem retrieving key (%" PRIX64
509 * getkeysigs - Gets a linked list of the signatures on a key.
510 * @keyid: The keyid to get the sigs for.
511 * @revoked: If the key is revoked.
513 * This function gets the list of signatures on a key. Used for key
514 * indexing and doing stats bits.
516 static struct ll *pg_getkeysigs(uint64_t keyid, bool *revoked)
518 struct ll *sigs = NULL;
519 PGresult *result = NULL;
521 char statement[1024];
524 bool intrans = false;
528 result = PQexec(dbconn, "BEGIN");
532 snprintf(statement, 1023,
533 "SELECT DISTINCT signer FROM onak_sigs WHERE signee = '%"
536 result = PQexec(dbconn, statement);
538 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
539 numsigs = PQntuples(result);
540 for (i = 0; i < numsigs; i++) {
543 str = PQgetvalue(result, i, 0);
544 while (str[j] != 0) {
546 if (str[j] >= '0' && str[j] <= '9') {
547 signer += str[j] - '0';
549 signer += str[j] - 'A' + 10;
553 sigs = lladd(sigs, createandaddtohash(signer));
555 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
556 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
562 result = PQexec(dbconn, "COMMIT");
567 * TODO: What do we do about revocations? We don't have the details
568 * stored in a separate table, so we'd have to grab the key and decode
569 * it, which we're trying to avoid by having a signers table.
571 if (revoked != NULL) {
579 * iterate_keys - call a function once for each key in the db.
580 * @iterfunc: The function to call.
581 * @ctx: A context pointer
583 * Calls iterfunc once for each key in the database. ctx is passed
584 * unaltered to iterfunc. This function is intended to aid database dumps
585 * and statistic calculations.
587 * Returns the number of keys we iterated over.
589 static int pg_iterate_keys(void (*iterfunc)(void *ctx,
590 struct openpgp_publickey *key), void *ctx)
592 struct openpgp_packet_list *packets = NULL;
593 struct openpgp_publickey *key = NULL;
594 PGresult *result = NULL;
596 char statement[1024];
602 result = PQexec(dbconn, "SELECT keydata FROM onak_keys;");
604 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
605 numkeys = PQntuples(result);
606 for (i = 0; i < numkeys; i++) {
607 oids = PQgetvalue(result, i, 0);
608 key_oid = (Oid) atoi(oids);
610 fd = lo_open(dbconn, key_oid, INV_READ);
612 logthing(LOGTHING_ERROR,
613 "Can't open large object.");
615 read_openpgp_stream(keydb_fetchchar, &fd,
617 parse_keys(packets, &key);
618 lo_close(dbconn, fd);
624 free_packet_list(packets);
628 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
629 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
638 * Include the basic keydb routines.
640 #define NEED_GETFULLKEYID 1
641 #define NEED_UPDATEKEYS 1
644 struct dbfuncs keydb_pg_funcs = {
646 .cleanupdb = pg_cleanupdb,
647 .starttrans = pg_starttrans,
648 .endtrans = pg_endtrans,
649 .fetch_key = pg_fetch_key,
650 .fetch_key_text = pg_fetch_key_text,
651 .store_key = pg_store_key,
652 .update_keys = generic_update_keys,
653 .delete_key = pg_delete_key,
654 .getkeysigs = pg_getkeysigs,
655 .cached_getkeysigs = generic_cached_getkeysigs,
656 .keyid2uid = pg_keyid2uid,
657 .getfullkeyid = generic_getfullkeyid,
658 .iterate_keys = pg_iterate_keys,