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 = '%llX'",
156 snprintf(statement, 1023,
157 "SELECT keydata FROM onak_keys WHERE keyid "
161 result = PQexec(dbconn, statement);
163 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
164 numkeys = PQntuples(result);
165 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
166 oids = PQgetvalue(result, i, 0);
167 key_oid = (Oid) atoi(oids);
169 fd = lo_open(dbconn, key_oid, INV_READ);
171 logthing(LOGTHING_ERROR,
172 "Can't open large object.");
174 read_openpgp_stream(keydb_fetchchar, &fd,
176 parse_keys(packets, publickey);
177 lo_close(dbconn, fd);
178 free_packet_list(packets);
182 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
183 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
189 result = PQexec(dbconn, "COMMIT");
196 * fetch_key_text - Trys to find the keys that contain the supplied text.
197 * @search: The text to search for.
198 * @publickey: A pointer to a structure to return the key in.
200 * This function searches for the supplied text and returns the keys that
203 static int pg_fetch_key_text(const char *search,
204 struct openpgp_publickey **publickey)
206 struct openpgp_packet_list *packets = NULL;
207 PGresult *result = NULL;
209 char statement[1024];
214 char *newsearch = NULL;
216 result = PQexec(dbconn, "BEGIN");
219 newsearch = malloc(strlen(search) * 2 + 1);
220 memset(newsearch, 0, strlen(search) * 2 + 1);
221 PQescapeStringConn(dbconn, newsearch, search, strlen(search), NULL);
222 snprintf(statement, 1023,
223 "SELECT DISTINCT onak_keys.keydata FROM onak_keys, "
224 "onak_uids WHERE onak_keys.keyid = onak_uids.keyid "
225 "AND onak_uids.uid LIKE '%%%s%%'",
227 result = PQexec(dbconn, statement);
231 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
232 numkeys = PQntuples(result);
233 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
234 oids = PQgetvalue(result, i, 0);
235 key_oid = (Oid) atoi(oids);
237 fd = lo_open(dbconn, key_oid, INV_READ);
239 logthing(LOGTHING_ERROR,
240 "Can't open large object.");
242 read_openpgp_stream(keydb_fetchchar, &fd,
245 parse_keys(packets, publickey);
246 lo_close(dbconn, fd);
247 free_packet_list(packets);
251 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
252 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
257 result = PQexec(dbconn, "COMMIT");
263 * delete_key - Given a keyid delete the key from storage.
264 * @keyid: The keyid to delete.
265 * @intrans: If we're already in a transaction.
267 * This function deletes a public key from whatever storage mechanism we
268 * are using. Returns 0 if the key existed.
270 static int pg_delete_key(uint64_t keyid, bool intrans)
272 PGresult *result = NULL;
274 char statement[1024];
280 result = PQexec(dbconn, "BEGIN");
284 snprintf(statement, 1023,
285 "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
287 result = PQexec(dbconn, statement);
289 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
291 i = PQntuples(result);
293 oids = PQgetvalue(result, i-1, 0);
294 key_oid = (Oid) atoi(oids);
295 lo_unlink(dbconn, key_oid);
300 snprintf(statement, 1023,
301 "DELETE FROM onak_keys WHERE keyid = '%llX'",
303 result = PQexec(dbconn, statement);
306 snprintf(statement, 1023,
307 "DELETE FROM onak_sigs WHERE signee = '%llX'",
309 result = PQexec(dbconn, statement);
312 snprintf(statement, 1023,
313 "DELETE FROM onak_uids WHERE keyid = '%llX'",
315 result = PQexec(dbconn, statement);
316 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
317 logthing(LOGTHING_ERROR,
318 "Problem retrieving key (%llX) from DB.",
325 result = PQexec(dbconn, "COMMIT");
332 * store_key - Takes a key and stores it.
333 * @publickey: A pointer to the public key to store.
334 * @intrans: If we're already in a transaction.
335 * @update: If true the key exists and should be updated.
337 * Again we just use the hex representation of the keyid as the filename
338 * to store the key to. We flatten the public key to a list of OpenPGP
339 * packets and then use write_openpgp_stream() to write the stream out to
340 * the file. If update is true then we delete the old key first, otherwise
341 * we trust that it doesn't exist.
343 static int pg_store_key(struct openpgp_publickey *publickey, bool intrans,
346 struct openpgp_packet_list *packets = NULL;
347 struct openpgp_packet_list *list_end = NULL;
348 struct openpgp_publickey *next = NULL;
349 struct openpgp_signedpacket_list *curuid = NULL;
350 PGresult *result = NULL;
351 char statement[1024];
355 char *primary = NULL;
356 char *safeuid = NULL;
360 result = PQexec(dbconn, "BEGIN");
365 * Delete the key if we already have it.
367 * TODO: Can we optimize this perhaps? Possibly when other data is
368 * involved as well? I suspect this is easiest and doesn't make a lot
369 * of difference though - the largest chunk of data is the keydata and
370 * it definitely needs updated.
373 pg_delete_key(get_keyid(publickey), true);
376 next = publickey->next;
377 publickey->next = NULL;
378 flatten_publickey(publickey, &packets, &list_end);
379 publickey->next = next;
381 key_oid = lo_creat(dbconn, INV_READ | INV_WRITE);
383 logthing(LOGTHING_ERROR, "Can't create key OID");
385 fd = lo_open(dbconn, key_oid, INV_WRITE);
386 write_openpgp_stream(keydb_putchar, &fd, packets);
387 lo_close(dbconn, fd);
389 free_packet_list(packets);
392 snprintf(statement, 1023,
393 "INSERT INTO onak_keys (keyid, keydata) VALUES "
395 get_keyid(publickey),
397 result = PQexec(dbconn, statement);
399 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
400 logthing(LOGTHING_ERROR, "Problem storing key in DB.");
401 logthing(LOGTHING_ERROR, "%s", PQresultErrorMessage(result));
405 uids = keyuids(publickey, &primary);
407 for (i = 0; uids[i] != NULL; i++) {
408 safeuid = malloc(strlen(uids[i]) * 2 + 1);
409 if (safeuid != NULL) {
410 memset(safeuid, 0, strlen(uids[i]) * 2 + 1);
411 PQescapeStringConn(dbconn, safeuid, uids[i],
412 strlen(uids[i]), NULL);
414 snprintf(statement, 1023,
415 "INSERT INTO onak_uids "
417 "VALUES ('%llX', '%s', '%c')",
418 get_keyid(publickey),
420 (uids[i] == primary) ? 't' : 'f');
421 result = PQexec(dbconn, statement);
426 if (uids[i] != NULL) {
431 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
432 logthing(LOGTHING_ERROR,
433 "Problem storing key in DB.");
434 logthing(LOGTHING_ERROR, "%s",
435 PQresultErrorMessage(result));
438 * TODO: Check result.
446 for (curuid = publickey->uids; curuid != NULL; curuid = curuid->next) {
447 for (packets = curuid->sigs; packets != NULL;
448 packets = packets->next) {
449 snprintf(statement, 1023,
450 "INSERT INTO onak_sigs (signer, signee) "
451 "VALUES ('%llX', '%llX')",
452 sig_keyid(packets->packet),
453 get_keyid(publickey));
454 result = PQexec(dbconn, statement);
460 result = PQexec(dbconn, "COMMIT");
468 * keyid2uid - Takes a keyid and returns the primary UID for it.
469 * @keyid: The keyid to lookup.
471 static char *pg_keyid2uid(uint64_t keyid)
473 PGresult *result = NULL;
474 char statement[1024];
477 snprintf(statement, 1023,
478 "SELECT uid FROM onak_uids WHERE keyid = '%llX' AND pri = 't'",
480 result = PQexec(dbconn, statement);
483 * Technically we only expect one response to the query; a key only has
484 * one primary ID. Better to return something than nothing though.
486 * TODO: Log if we get more than one response? Needs logging framework
489 if (PQresultStatus(result) == PGRES_TUPLES_OK &&
490 PQntuples(result) >= 1) {
491 uid = strdup(PQgetvalue(result, 0, 0));
492 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
493 logthing(LOGTHING_ERROR,
494 "Problem retrieving key (%llX) from DB.",
504 * getkeysigs - Gets a linked list of the signatures on a key.
505 * @keyid: The keyid to get the sigs for.
506 * @revoked: If the key is revoked.
508 * This function gets the list of signatures on a key. Used for key
509 * indexing and doing stats bits.
511 static struct ll *pg_getkeysigs(uint64_t keyid, bool *revoked)
513 struct ll *sigs = NULL;
514 PGresult *result = NULL;
516 char statement[1024];
519 bool intrans = false;
523 result = PQexec(dbconn, "BEGIN");
527 snprintf(statement, 1023,
528 "SELECT DISTINCT signer FROM onak_sigs WHERE signee = '%llX'",
530 result = PQexec(dbconn, statement);
532 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
533 numsigs = PQntuples(result);
534 for (i = 0; i < numsigs; i++) {
537 str = PQgetvalue(result, i, 0);
538 while (str[j] != 0) {
540 if (str[j] >= '0' && str[j] <= '9') {
541 signer += str[j] - '0';
543 signer += str[j] - 'A' + 10;
547 sigs = lladd(sigs, createandaddtohash(signer));
549 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
550 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
556 result = PQexec(dbconn, "COMMIT");
561 * TODO: What do we do about revocations? We don't have the details
562 * stored in a separate table, so we'd have to grab the key and decode
563 * it, which we're trying to avoid by having a signers table.
565 if (revoked != NULL) {
573 * iterate_keys - call a function once for each key in the db.
574 * @iterfunc: The function to call.
575 * @ctx: A context pointer
577 * Calls iterfunc once for each key in the database. ctx is passed
578 * unaltered to iterfunc. This function is intended to aid database dumps
579 * and statistic calculations.
581 * Returns the number of keys we iterated over.
583 static int pg_iterate_keys(void (*iterfunc)(void *ctx,
584 struct openpgp_publickey *key), void *ctx)
586 struct openpgp_packet_list *packets = NULL;
587 struct openpgp_publickey *key = NULL;
588 PGresult *result = NULL;
590 char statement[1024];
596 result = PQexec(dbconn, "SELECT keydata FROM onak_keys;");
598 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
599 numkeys = PQntuples(result);
600 for (i = 0; i < numkeys; i++) {
601 oids = PQgetvalue(result, i, 0);
602 key_oid = (Oid) atoi(oids);
604 fd = lo_open(dbconn, key_oid, INV_READ);
606 logthing(LOGTHING_ERROR,
607 "Can't open large object.");
609 read_openpgp_stream(keydb_fetchchar, &fd,
611 parse_keys(packets, &key);
612 lo_close(dbconn, fd);
618 free_packet_list(packets);
622 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
623 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
632 * Include the basic keydb routines.
634 #define NEED_GETFULLKEYID 1
635 #define NEED_UPDATEKEYS 1
638 struct dbfuncs keydb_pg_funcs = {
640 .cleanupdb = pg_cleanupdb,
641 .starttrans = pg_starttrans,
642 .endtrans = pg_endtrans,
643 .fetch_key = pg_fetch_key,
644 .fetch_key_text = pg_fetch_key_text,
645 .store_key = pg_store_key,
646 .update_keys = generic_update_keys,
647 .delete_key = pg_delete_key,
648 .getkeysigs = pg_getkeysigs,
649 .cached_getkeysigs = generic_cached_getkeysigs,
650 .keyid2uid = pg_keyid2uid,
651 .getfullkeyid = generic_getfullkeyid,
652 .iterate_keys = pg_iterate_keys,