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, void *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, void *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;
 
 601         result = PQexec(dbconn, "SELECT keydata FROM onak_keys;");
 
 603         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
 
 604                 numkeys = PQntuples(result);
 
 605                 for (i = 0; i < numkeys; i++) {
 
 606                         oids = PQgetvalue(result, i, 0);
 
 607                         key_oid = (Oid) atoi(oids);
 
 609                         fd = lo_open(dbconn, key_oid, INV_READ);
 
 611                                 logthing(LOGTHING_ERROR,
 
 612                                                 "Can't open large object.");
 
 614                                 read_openpgp_stream(keydb_fetchchar, &fd,
 
 616                                 parse_keys(packets, &key);
 
 617                                 lo_close(dbconn, fd);
 
 623                                 free_packet_list(packets);
 
 627         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
 
 628                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
 
 637  * Include the basic keydb routines.
 
 639 #define NEED_GETFULLKEYID 1
 
 640 #define NEED_UPDATEKEYS 1
 
 643 struct dbfuncs keydb_pg_funcs = {
 
 645         .cleanupdb              = pg_cleanupdb,
 
 646         .starttrans             = pg_starttrans,
 
 647         .endtrans               = pg_endtrans,
 
 648         .fetch_key              = pg_fetch_key,
 
 649         .fetch_key_text         = pg_fetch_key_text,
 
 650         .store_key              = pg_store_key,
 
 651         .update_keys            = generic_update_keys,
 
 652         .delete_key             = pg_delete_key,
 
 653         .getkeysigs             = pg_getkeysigs,
 
 654         .cached_getkeysigs      = generic_cached_getkeysigs,
 
 655         .keyid2uid              = pg_keyid2uid,
 
 656         .getfullkeyid           = generic_getfullkeyid,
 
 657         .iterate_keys           = pg_iterate_keys,