2 * keydb_pg.c - Routines to store and fetch keys in a PostGres database.
4 * Jonathan McDowell <noodles@earth.li>
6 * Copyright 2002 Project Purple
8 * $Id: keydb_pg.c,v 1.14 2004/03/23 12:33:47 noodles Exp $
11 #include <postgresql/libpq-fe.h>
12 #include <postgresql/libpq/libpq-fs.h>
14 //#include <libpq-fe.h>
15 //#include <libpq/libpq-fs.h>
16 #include <sys/types.h>
28 #include "decodekey.h"
29 #include "keystructs.h"
32 #include "onak-conf.h"
36 * dbconn - our connection to the database.
38 static PGconn *dbconn = NULL;
41 * keydb_fetchchar - Fetches a char from a file.
43 static int keydb_fetchchar(void *fd, size_t count, unsigned char *c)
45 return (!lo_read(dbconn, *(int *) fd, c, count));
49 * keydb_putchar - Puts a char to a file.
51 static int keydb_putchar(void *fd, size_t count, unsigned char *c)
53 return !(lo_write(dbconn, *(int *) fd, c, count));
57 * initdb - Initialize the key database.
59 * This function should be called before any of the other functions in
60 * this file are called in order to allow the DB to be initialized ready
63 void initdb(bool readonly)
65 dbconn = PQsetdbLogin(config.pg_dbhost, // host
69 config.pg_dbname, // database
70 config.pg_dbuser, //login
71 config.pg_dbpass); // password
73 if (PQstatus(dbconn) == CONNECTION_BAD) {
74 logthing(LOGTHING_CRITICAL, "Connection to database failed.");
75 logthing(LOGTHING_CRITICAL, "%s", PQerrorMessage(dbconn));
83 * cleanupdb - De-initialize the key database.
85 * This function should be called upon program exit to allow the DB to
86 * cleanup after itself.
95 * starttrans - Start a transaction.
97 * Start a transaction. Intended to be used if we're about to perform many
98 * operations on the database to help speed it all up, or if we want
99 * something to only succeed if all relevant operations are successful.
101 bool starttrans(void)
103 PGresult *result = NULL;
105 result = PQexec(dbconn, "BEGIN");
112 * endtrans - End a transaction.
114 * Ends a transaction.
118 PGresult *result = NULL;
120 result = PQexec(dbconn, "COMMIT");
127 * fetch_key - Given a keyid fetch the key from storage.
128 * @keyid: The keyid to fetch.
129 * @publickey: A pointer to a structure to return the key in.
130 * @intrans: If we're already in a transaction.
132 * We use the hex representation of the keyid as the filename to fetch the
133 * key from. The key is stored in the file as a binary OpenPGP stream of
134 * packets, so we can just use read_openpgp_stream() to read the packets
135 * in and then parse_keys() to parse the packets into a publickey
138 int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey,
141 struct openpgp_packet_list *packets = NULL;
142 PGresult *result = NULL;
144 char statement[1024];
151 result = PQexec(dbconn, "BEGIN");
155 if (keyid > 0xFFFFFFFF) {
156 snprintf(statement, 1023,
157 "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
160 snprintf(statement, 1023,
161 "SELECT keydata FROM onak_keys WHERE keyid "
165 result = PQexec(dbconn, statement);
167 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
168 numkeys = PQntuples(result);
169 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
170 oids = PQgetvalue(result, i, 0);
171 key_oid = (Oid) atoi(oids);
173 fd = lo_open(dbconn, key_oid, INV_READ);
175 logthing(LOGTHING_ERROR,
176 "Can't open large object.");
178 read_openpgp_stream(keydb_fetchchar, &fd,
180 parse_keys(packets, publickey);
181 lo_close(dbconn, fd);
182 free_packet_list(packets);
186 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
187 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
193 result = PQexec(dbconn, "COMMIT");
200 * fetch_key_text - Trys to find the keys that contain the supplied text.
201 * @search: The text to search for.
202 * @publickey: A pointer to a structure to return the key in.
204 * This function searches for the supplied text and returns the keys that
207 int fetch_key_text(const char *search, struct openpgp_publickey **publickey)
209 struct openpgp_packet_list *packets = NULL;
210 PGresult *result = NULL;
212 char statement[1024];
217 char *newsearch = NULL;
219 result = PQexec(dbconn, "BEGIN");
222 newsearch = malloc(strlen(search) * 2 + 1);
223 memset(newsearch, 0, strlen(search) * 2 + 1);
224 PQescapeString(newsearch, search, strlen(search));
225 snprintf(statement, 1023,
226 "SELECT DISTINCT onak_keys.keydata FROM onak_keys, "
227 "onak_uids WHERE onak_keys.keyid = onak_uids.keyid "
228 "AND onak_uids.uid LIKE '%%%s%%'",
230 result = PQexec(dbconn, statement);
234 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
235 numkeys = PQntuples(result);
236 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
237 oids = PQgetvalue(result, i, 0);
238 key_oid = (Oid) atoi(oids);
240 fd = lo_open(dbconn, key_oid, INV_READ);
242 logthing(LOGTHING_ERROR,
243 "Can't open large object.");
245 read_openpgp_stream(keydb_fetchchar, &fd,
247 parse_keys(packets, publickey);
248 lo_close(dbconn, fd);
249 free_packet_list(packets);
253 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
254 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
259 result = PQexec(dbconn, "COMMIT");
265 * store_key - Takes a key and stores it.
266 * @publickey: A pointer to the public key to store.
267 * @intrans: If we're already in a transaction.
268 * @update: If true the key exists and should be updated.
270 * Again we just use the hex representation of the keyid as the filename
271 * to store the key to. We flatten the public key to a list of OpenPGP
272 * packets and then use write_openpgp_stream() to write the stream out to
273 * the file. If update is true then we delete the old key first, otherwise
274 * we trust that it doesn't exist.
276 int store_key(struct openpgp_publickey *publickey, bool intrans, bool update)
278 struct openpgp_packet_list *packets = NULL;
279 struct openpgp_packet_list *list_end = NULL;
280 struct openpgp_publickey *next = NULL;
281 struct openpgp_signedpacket_list *curuid = NULL;
282 PGresult *result = NULL;
283 char statement[1024];
287 char *primary = NULL;
288 char *safeuid = NULL;
292 result = PQexec(dbconn, "BEGIN");
297 * Delete the key if we already have it.
299 * TODO: Can we optimize this perhaps? Possibly when other data is
300 * involved as well? I suspect this is easiest and doesn't make a lot
301 * of difference though - the largest chunk of data is the keydata and
302 * it definitely needs updated.
305 delete_key(get_keyid(publickey), true);
308 next = publickey->next;
309 publickey->next = NULL;
310 flatten_publickey(publickey, &packets, &list_end);
311 publickey->next = next;
313 key_oid = lo_creat(dbconn, INV_READ | INV_WRITE);
315 logthing(LOGTHING_ERROR, "Can't create key OID");
317 fd = lo_open(dbconn, key_oid, INV_WRITE);
318 write_openpgp_stream(keydb_putchar, &fd, packets);
319 lo_close(dbconn, fd);
321 free_packet_list(packets);
324 snprintf(statement, 1023,
325 "INSERT INTO onak_keys (keyid, keydata) VALUES "
327 get_keyid(publickey),
329 result = PQexec(dbconn, statement);
331 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
332 logthing(LOGTHING_ERROR, "Problem storing key in DB.");
333 logthing(LOGTHING_ERROR, "%s", PQresultErrorMessage(result));
337 uids = keyuids(publickey, &primary);
339 for (i = 0; uids[i] != NULL; i++) {
340 safeuid = malloc(strlen(uids[i]) * 2 + 1);
341 if (safeuid != NULL) {
342 memset(safeuid, 0, strlen(uids[i]) * 2 + 1);
343 PQescapeString(safeuid, uids[i],
346 snprintf(statement, 1023,
347 "INSERT INTO onak_uids "
349 "VALUES ('%llX', '%s', '%c')",
350 get_keyid(publickey),
352 (uids[i] == primary) ? 't' : 'f');
353 result = PQexec(dbconn, statement);
358 if (uids[i] != NULL) {
363 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
364 logthing(LOGTHING_ERROR,
365 "Problem storing key in DB.");
366 logthing(LOGTHING_ERROR, "%s",
367 PQresultErrorMessage(result));
370 * TODO: Check result.
378 for (curuid = publickey->uids; curuid != NULL; curuid = curuid->next) {
379 for (packets = curuid->sigs; packets != NULL;
380 packets = packets->next) {
381 snprintf(statement, 1023,
382 "INSERT INTO onak_sigs (signer, signee) "
383 "VALUES ('%llX', '%llX')",
384 sig_keyid(packets->packet),
385 get_keyid(publickey));
386 result = PQexec(dbconn, statement);
392 result = PQexec(dbconn, "COMMIT");
400 * delete_key - Given a keyid delete the key from storage.
401 * @keyid: The keyid to delete.
402 * @intrans: If we're already in a transaction.
404 * This function deletes a public key from whatever storage mechanism we
405 * are using. Returns 0 if the key existed.
407 int delete_key(uint64_t keyid, bool intrans)
409 PGresult *result = NULL;
411 char statement[1024];
417 result = PQexec(dbconn, "BEGIN");
421 snprintf(statement, 1023,
422 "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
424 result = PQexec(dbconn, statement);
426 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
428 i = PQntuples(result);
430 oids = PQgetvalue(result, i-1, 0);
431 key_oid = (Oid) atoi(oids);
432 lo_unlink(dbconn, key_oid);
437 snprintf(statement, 1023,
438 "DELETE FROM onak_keys WHERE keyid = '%llX'",
440 result = PQexec(dbconn, statement);
443 snprintf(statement, 1023,
444 "DELETE FROM onak_sigs WHERE signee = '%llX'",
446 result = PQexec(dbconn, statement);
449 snprintf(statement, 1023,
450 "DELETE FROM onak_uids WHERE keyid = '%llX'",
452 result = PQexec(dbconn, statement);
453 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
454 logthing(LOGTHING_ERROR,
455 "Problem retrieving key (%llX) from DB.",
462 result = PQexec(dbconn, "COMMIT");
469 * keyid2uid - Takes a keyid and returns the primary UID for it.
470 * @keyid: The keyid to lookup.
472 char *keyid2uid(uint64_t keyid)
474 PGresult *result = NULL;
475 char statement[1024];
478 snprintf(statement, 1023,
479 "SELECT uid FROM onak_uids WHERE keyid = '%llX' AND pri = 't'",
481 result = PQexec(dbconn, statement);
484 * Technically we only expect one response to the query; a key only has
485 * one primary ID. Better to return something than nothing though.
487 * TODO: Log if we get more than one response? Needs logging framework
490 if (PQresultStatus(result) == PGRES_TUPLES_OK &&
491 PQntuples(result) >= 1) {
492 uid = strdup(PQgetvalue(result, 0, 0));
493 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
494 logthing(LOGTHING_ERROR,
495 "Problem retrieving key (%llX) from DB.",
505 * getkeysigs - Gets a linked list of the signatures on a key.
506 * @keyid: The keyid to get the sigs for.
507 * @revoked: If the key is revoked.
509 * This function gets the list of signatures on a key. Used for key
510 * indexing and doing stats bits.
512 struct ll *getkeysigs(uint64_t keyid, bool *revoked)
514 struct ll *sigs = NULL;
515 PGresult *result = NULL;
517 char statement[1024];
520 bool intrans = false;
524 result = PQexec(dbconn, "BEGIN");
528 snprintf(statement, 1023,
529 "SELECT DISTINCT signer FROM onak_sigs WHERE signee = '%llX'",
531 result = PQexec(dbconn, statement);
533 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
534 numsigs = PQntuples(result);
535 for (i = 0; i < numsigs; i++) {
538 str = PQgetvalue(result, i, 0);
539 while (str[j] != 0) {
541 if (str[j] >= '0' && str[j] <= '9') {
542 signer += str[j] - '0';
544 signer += str[j] - 'A' + 10;
548 sigs = lladd(sigs, createandaddtohash(signer));
550 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
551 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
557 result = PQexec(dbconn, "COMMIT");
562 * TODO: What do we do about revocations? We don't have the details
563 * stored in a separate table, so we'd have to grab the key and decode
564 * it, which we're trying to avoid by having a signers table.
566 if (revoked != NULL) {
574 * dumpdb - dump the key database
575 * @filenamebase: The base filename to use for the dump.
577 * Dumps the database into one or more files, which contain pure OpenPGP
578 * that can be reimported into onak or gpg. filenamebase provides a base
579 * file name for the dump; several files may be created, all of which will
580 * begin with this string and then have a unique number and a .pgp
583 int dumpdb(char *filenamebase)
589 * Include the basic keydb routines.
591 #define NEED_GETFULLKEYID 1