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
9 #include <postgresql/libpq-fe.h>
10 #include <postgresql/libpq/libpq-fs.h>
12 //#include <libpq-fe.h>
13 //#include <libpq/libpq-fs.h>
14 #include <sys/types.h>
26 #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, 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, 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
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 fprintf(stderr, "Connection to database failed.\n");
71 fprintf(stderr, "%s\n", 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.
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.
99 PGresult *result = NULL;
101 result = PQexec(dbconn, "BEGIN");
108 * endtrans - End a transaction.
110 * Ends a transaction.
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 int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey, bool intrans)
136 struct openpgp_packet_list *packets = NULL;
137 PGresult *result = NULL;
139 char statement[1024];
146 result = PQexec(dbconn, "BEGIN");
150 if (keyid > 0xFFFFFFFF) {
151 snprintf(statement, 1023,
152 "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
155 snprintf(statement, 1023,
156 "SELECT keydata FROM onak_keys WHERE keyid "
160 result = PQexec(dbconn, statement);
162 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
163 numkeys = PQntuples(result);
164 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
165 oids = PQgetvalue(result, i, 0);
166 key_oid = (Oid) atoi(oids);
168 fd = lo_open(dbconn, key_oid, INV_READ);
170 fprintf(stderr, "Can't open large object.\n");
172 read_openpgp_stream(keydb_fetchchar, &fd,
174 parse_keys(packets, publickey);
175 lo_close(dbconn, fd);
178 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
179 fprintf(stderr, "Problem retrieving key from DB.\n");
185 result = PQexec(dbconn, "COMMIT");
192 * fetch_key_text - Trys to find the keys that contain the supplied text.
193 * @search: The text to search for.
194 * @publickey: A pointer to a structure to return the key in.
196 * This function searches for the supplied text and returns the keys that
199 int fetch_key_text(const char *search, struct openpgp_publickey **publickey)
201 struct openpgp_packet_list *packets = NULL;
202 PGresult *result = NULL;
204 char statement[1024];
209 char *dodgychar = NULL;
211 result = PQexec(dbconn, "BEGIN");
215 * TODO: We really want to use PQescapeString, but this isn't supported
216 * by the version of Postgresql in Debian Stable. Roll on Woody and for
219 dodgychar = strchr(search, '\'');
220 while (dodgychar != NULL) {
222 dodgychar = strchr(search, '\'');
224 dodgychar = strchr(search, '\\');
225 while (dodgychar != NULL) {
227 dodgychar = strchr(search, '\\');
231 snprintf(statement, 1023,
232 "SELECT DISTINCT onak_keys.keydata FROM onak_keys, "
233 "onak_uids WHERE onak_keys.keyid = onak_uids.keyid "
234 "AND onak_uids.uid LIKE '%%%s%%'",
236 result = PQexec(dbconn, statement);
238 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
239 numkeys = PQntuples(result);
240 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
241 oids = PQgetvalue(result, i, 0);
242 key_oid = (Oid) atoi(oids);
244 fd = lo_open(dbconn, key_oid, INV_READ);
246 fprintf(stderr, "Can't open large object.\n");
248 read_openpgp_stream(keydb_fetchchar, &fd,
250 parse_keys(packets, publickey);
251 lo_close(dbconn, fd);
254 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
255 fprintf(stderr, "Problem retrieving key from DB.\n");
260 result = PQexec(dbconn, "COMMIT");
266 * store_key - Takes a key and stores it.
267 * @publickey: A pointer to the public key to store.
268 * @intrans: If we're already in a transaction.
269 * @update: If true the key exists and should be updated.
271 * Again we just use the hex representation of the keyid as the filename
272 * to store the key to. We flatten the public key to a list of OpenPGP
273 * packets and then use write_openpgp_stream() to write the stream out to
274 * the file. If update is true then we delete the old key first, otherwise we
275 * trust that it doesn't exist.
277 int store_key(struct openpgp_publickey *publickey, bool intrans, bool update)
279 struct openpgp_packet_list *packets = NULL;
280 struct openpgp_packet_list *list_end = NULL;
281 struct openpgp_publickey *next = NULL;
282 PGresult *result = NULL;
283 char statement[1024];
287 char *primary = NULL;
288 char *dodgychar = 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 fprintf(stderr, "Can't create key OID\n");
317 fd = lo_open(dbconn, key_oid, INV_WRITE);
318 write_openpgp_stream(keydb_putchar, &fd, packets);
319 lo_close(dbconn, fd);
322 snprintf(statement, 1023,
323 "INSERT INTO onak_keys (keyid, keydata) VALUES "
325 get_keyid(publickey),
327 result = PQexec(dbconn, statement);
329 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
330 fprintf(stderr, "Problem storing key in DB.\n");
331 fprintf(stderr, "%s\n", PQresultErrorMessage(result));
335 uids = keyuids(publickey, &primary);
337 for (i = 0; uids[i] != NULL; i++) {
339 * TODO: We really want to use PQescapeString, but this
340 * isn't supported by the version of Postgresql in
341 * Debian Stable. Roll on Woody and for now kludge it.
343 dodgychar = strchr(uids[i], '\'');
344 while (dodgychar != NULL) {
346 dodgychar = strchr(uids[i], '\'');
348 dodgychar = strchr(uids[i], '\\');
349 while (dodgychar != NULL) {
351 dodgychar = strchr(uids[i], '\\');
354 snprintf(statement, 1023,
355 "INSERT INTO onak_uids (keyid, uid, pri) "
356 "VALUES ('%llX', '%s', '%c')",
357 get_keyid(publickey),
359 (uids[i] == primary) ? 't' : 'f');
360 result = PQexec(dbconn, statement);
362 if (PQresultStatus(result) != PGRES_COMMAND_OK) {
363 fprintf(stderr, "Problem storing key in DB.\n");
364 fprintf(stderr, "%s\n",
365 PQresultErrorMessage(result));
368 * TODO: Check result.
375 result = PQexec(dbconn, "COMMIT");
383 * delete_key - Given a keyid delete the key from storage.
384 * @keyid: The keyid to delete.
385 * @intrans: If we're already in a transaction.
387 * This function deletes a public key from whatever storage mechanism we
388 * are using. Returns 0 if the key existed.
390 int delete_key(uint64_t keyid, bool intrans)
392 PGresult *result = NULL;
394 char statement[1024];
400 result = PQexec(dbconn, "BEGIN");
404 snprintf(statement, 1023,
405 "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
407 result = PQexec(dbconn, statement);
409 if (PQresultStatus(result) == PGRES_TUPLES_OK) {
411 i = PQntuples(result);
413 oids = PQgetvalue(result, i-1, 0);
414 key_oid = (Oid) atoi(oids);
415 lo_unlink(dbconn, key_oid);
420 snprintf(statement, 1023,
421 "DELETE FROM onak_keys WHERE keyid = '%llX'",
423 result = PQexec(dbconn, statement);
426 snprintf(statement, 1023,
427 "DELETE FROM onak_uids WHERE keyid = '%llX'",
429 result = PQexec(dbconn, statement);
430 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
431 fprintf(stderr, "Problem retrieving key (%llX) from DB.\n",
438 result = PQexec(dbconn, "COMMIT");
445 * keyid2uid - Takes a keyid and returns the primary UID for it.
446 * @keyid: The keyid to lookup.
448 char *keyid2uid(uint64_t keyid)
450 PGresult *result = NULL;
451 char statement[1024];
454 snprintf(statement, 1023,
455 "SELECT uid FROM onak_uids WHERE keyid = '%llX' AND pri = 't'",
457 result = PQexec(dbconn, statement);
460 * Technically we only expect one response to the query; a key only has
461 * one primary ID. Better to return something than nothing though.
463 * TODO: Log if we get more than one response? Needs logging framework
466 if (PQresultStatus(result) == PGRES_TUPLES_OK &&
467 PQntuples(result) >= 1) {
468 uid = strdup(PQgetvalue(result, 0, 0));
469 } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
470 fprintf(stderr, "Problem retrieving key (%llX) from DB.\n",
480 * Include the basic keydb routines.
482 #define NEED_GETKEYSIGS 1
483 #define NEED_GETFULLKEYID 1