]> git.sommitrealweird.co.uk Git - onak.git/blob - keydb_pg.c
Give Brett some credit.
[onak.git] / keydb_pg.c
1 /*
2  * keydb_pg.c - Routines to store and fetch keys in a PostGres database.
3  *
4  * Jonathan McDowell <noodles@earth.li>
5  *
6  * Copyright 2002-2004 Project Purple
7  */
8
9 #include <postgresql/libpq-fe.h>
10 #include <postgresql/libpq/libpq-fs.h>
11
12 #include <sys/types.h>
13 #include <sys/uio.h>
14 #include <errno.h>
15 #include <fcntl.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20
21 #include "hash.h"
22 #include "keydb.h"
23 #include "keyid.h"
24 #include "decodekey.h"
25 #include "keystructs.h"
26 #include "log.h"
27 #include "mem.h"
28 #include "onak-conf.h"
29 #include "parsekey.h"
30
31 /**
32  *      dbconn - our connection to the database.
33  */
34 static PGconn *dbconn = NULL;
35
36 /**
37  *      keydb_fetchchar - Fetches a char from a file.
38  */
39 static int keydb_fetchchar(void *fd, size_t count, unsigned char *c)
40 {
41         return (!lo_read(dbconn, *(int *) fd, (char *) c, count));
42 }
43
44 /**
45  *      keydb_putchar - Puts a char to a file.
46  */
47 static int keydb_putchar(void *fd, size_t count, unsigned char *c)
48 {
49         return !(lo_write(dbconn, *(int *) fd, (char *) c, count));
50 }
51
52 /**
53  *      initdb - Initialize the key database.
54  *
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
57  *      for access.
58  */
59 static void pg_initdb(bool readonly)
60 {
61         dbconn = PQsetdbLogin(config.pg_dbhost, // host
62                         NULL, // port
63                         NULL, // options
64                         NULL, // tty
65                         config.pg_dbname, // database
66                         config.pg_dbuser,  //login
67                         config.pg_dbpass); // password
68
69         if (PQstatus(dbconn) == CONNECTION_BAD) {
70                 logthing(LOGTHING_CRITICAL, "Connection to database failed.");
71                 logthing(LOGTHING_CRITICAL, "%s", PQerrorMessage(dbconn));
72                 PQfinish(dbconn);
73                 dbconn = NULL;
74                 exit(1);
75         }
76 }
77
78 /**
79  *      cleanupdb - De-initialize the key database.
80  *
81  *      This function should be called upon program exit to allow the DB to
82  *      cleanup after itself.
83  */
84 static void pg_cleanupdb(void)
85 {
86         PQfinish(dbconn);
87         dbconn = NULL;
88 }
89
90 /**
91  *      starttrans - Start a transaction.
92  *
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.
96  */
97 static bool pg_starttrans(void)
98 {
99         PGresult *result = NULL;
100         
101         result = PQexec(dbconn, "BEGIN");
102         PQclear(result);
103
104         return true;
105 }
106
107 /**
108  *      endtrans - End a transaction.
109  *
110  *      Ends a transaction.
111  */
112 static void pg_endtrans(void)
113 {
114         PGresult *result = NULL;
115
116         result = PQexec(dbconn, "COMMIT");
117         PQclear(result);
118
119         return;
120 }
121
122 /**
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.
127  *
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
132  *      structure.
133  */
134 static int pg_fetch_key(uint64_t keyid, struct openpgp_publickey **publickey,
135                 bool intrans)
136 {
137         struct openpgp_packet_list *packets = NULL;
138         PGresult *result = NULL;
139         char *oids = NULL;
140         char statement[1024];
141         int fd = -1;
142         int i = 0;
143         int numkeys = 0;
144         Oid key_oid;
145
146         if (!intrans) {
147                 result = PQexec(dbconn, "BEGIN");
148                 PQclear(result);
149         }
150         
151         if (keyid > 0xFFFFFFFF) {
152                 snprintf(statement, 1023,
153                         "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
154                         keyid);
155         } else {
156                 snprintf(statement, 1023,
157                         "SELECT keydata FROM onak_keys WHERE keyid "
158                         "LIKE '%%%llX'",
159                         keyid);
160         }
161         result = PQexec(dbconn, statement);
162
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);
168
169                         fd = lo_open(dbconn, key_oid, INV_READ);
170                         if (fd < 0) {
171                                 logthing(LOGTHING_ERROR,
172                                                 "Can't open large object.");
173                         } else {
174                                 read_openpgp_stream(keydb_fetchchar, &fd,
175                                                 &packets, 0);
176                                 parse_keys(packets, publickey);
177                                 lo_close(dbconn, fd);
178                                 free_packet_list(packets);
179                                 packets = NULL;
180                         }
181                 }
182         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
183                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
184         }
185
186         PQclear(result);
187
188         if (!intrans) {
189                 result = PQexec(dbconn, "COMMIT");
190                 PQclear(result);
191         }
192         return (numkeys);
193 }
194
195 /**
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.
199  *
200  *      This function searches for the supplied text and returns the keys that
201  *      contain it.
202  */
203 static int pg_fetch_key_text(const char *search,
204                 struct openpgp_publickey **publickey)
205 {
206         struct openpgp_packet_list *packets = NULL;
207         PGresult *result = NULL;
208         char *oids = NULL;
209         char statement[1024];
210         int fd = -1;
211         int i = 0;
212         int numkeys = 0;
213         Oid key_oid;
214         char *newsearch = NULL;
215
216         result = PQexec(dbconn, "BEGIN");
217         PQclear(result);
218
219         newsearch = malloc(strlen(search) * 2 + 1);
220         memset(newsearch, 0, strlen(search) * 2 + 1);
221         PQescapeString(newsearch, search, strlen(search));
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%%'",
226                         newsearch);
227         result = PQexec(dbconn, statement);
228         free(newsearch);
229         newsearch = NULL;
230
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);
236
237                         fd = lo_open(dbconn, key_oid, INV_READ);
238                         if (fd < 0) {
239                                 logthing(LOGTHING_ERROR,
240                                                 "Can't open large object.");
241                         } else {
242                                 read_openpgp_stream(keydb_fetchchar, &fd,
243                                                 &packets,
244                                                 0);
245                                 parse_keys(packets, publickey);
246                                 lo_close(dbconn, fd);
247                                 free_packet_list(packets);
248                                 packets = NULL;
249                         }
250                 }
251         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
252                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
253         }
254
255         PQclear(result);
256
257         result = PQexec(dbconn, "COMMIT");
258         PQclear(result);
259         return (numkeys);
260 }
261
262 /**
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.
266  *
267  *      This function deletes a public key from whatever storage mechanism we
268  *      are using. Returns 0 if the key existed.
269  */
270 static int pg_delete_key(uint64_t keyid, bool intrans)
271 {
272         PGresult *result = NULL;
273         char *oids = NULL;
274         char statement[1024];
275         int found = 1;
276         int i;
277         Oid key_oid;
278
279         if (!intrans) {
280                 result = PQexec(dbconn, "BEGIN");
281                 PQclear(result);
282         }
283         
284         snprintf(statement, 1023,
285                         "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
286                         keyid);
287         result = PQexec(dbconn, statement);
288
289         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
290                 found = 0;
291                 i = PQntuples(result);
292                 while (i > 0) {
293                         oids = PQgetvalue(result, i-1, 0);
294                         key_oid = (Oid) atoi(oids);
295                         lo_unlink(dbconn, key_oid);
296                         i--;
297                 }
298                 PQclear(result);
299
300                 snprintf(statement, 1023,
301                         "DELETE FROM onak_keys WHERE keyid = '%llX'",
302                         keyid);
303                 result = PQexec(dbconn, statement);
304                 PQclear(result);
305
306                 snprintf(statement, 1023,
307                         "DELETE FROM onak_sigs WHERE signee = '%llX'",
308                         keyid);
309                 result = PQexec(dbconn, statement);
310                 PQclear(result);
311
312                 snprintf(statement, 1023,
313                         "DELETE FROM onak_uids WHERE keyid = '%llX'",
314                         keyid);
315                 result = PQexec(dbconn, statement);
316         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
317                 logthing(LOGTHING_ERROR,
318                                 "Problem retrieving key (%llX) from DB.",
319                                 keyid);
320         }
321
322         PQclear(result);
323
324         if (!intrans) {
325                 result = PQexec(dbconn, "COMMIT");
326                 PQclear(result);
327         }
328         return (found);
329 }
330
331 /**
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.
336  *
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.
342  */
343 static int pg_store_key(struct openpgp_publickey *publickey, bool intrans,
344                 bool update)
345 {
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];
352         Oid key_oid;
353         int fd;
354         char **uids = NULL;
355         char *primary = NULL;
356         char *safeuid = NULL;
357         int i;
358
359         if (!intrans) {
360                 result = PQexec(dbconn, "BEGIN");
361                 PQclear(result);
362         }
363
364         /*
365          * Delete the key if we already have it.
366          *
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.
371          */
372         if (update) {
373                 pg_delete_key(get_keyid(publickey), true);
374         }
375
376         next = publickey->next;
377         publickey->next = NULL;
378         flatten_publickey(publickey, &packets, &list_end);
379         publickey->next = next;
380                 
381         key_oid = lo_creat(dbconn, INV_READ | INV_WRITE);
382         if (key_oid == 0) {
383                 logthing(LOGTHING_ERROR, "Can't create key OID");
384         } else {
385                 fd = lo_open(dbconn, key_oid, INV_WRITE);
386                 write_openpgp_stream(keydb_putchar, &fd, packets);
387                 lo_close(dbconn, fd);
388         }
389         free_packet_list(packets);
390         packets = NULL;
391
392         snprintf(statement, 1023, 
393                         "INSERT INTO onak_keys (keyid, keydata) VALUES "
394                         "('%llX', '%d')", 
395                         get_keyid(publickey),
396                         key_oid);
397         result = PQexec(dbconn, statement);
398
399         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
400                 logthing(LOGTHING_ERROR, "Problem storing key in DB.");
401                 logthing(LOGTHING_ERROR, "%s", PQresultErrorMessage(result));
402         }
403         PQclear(result);
404
405         uids = keyuids(publickey, &primary);
406         if (uids != NULL) {
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                                 PQescapeString(safeuid, uids[i],
412                                                 strlen(uids[i]));
413
414                                 snprintf(statement, 1023,
415                                         "INSERT INTO onak_uids "
416                                         "(keyid, uid, pri) "
417                                         "VALUES ('%llX', '%s', '%c')",
418                                         get_keyid(publickey),
419                                         safeuid,
420                                         (uids[i] == primary) ? 't' : 'f');
421                                 result = PQexec(dbconn, statement);
422
423                                 free(safeuid);
424                                 safeuid = NULL;
425                         }
426                         if (uids[i] != NULL) {
427                                 free(uids[i]);
428                                 uids[i] = NULL;
429                         }
430
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));
436                         }
437                         /*
438                          * TODO: Check result.
439                          */
440                         PQclear(result);
441                 }
442                 free(uids);
443                 uids = NULL;
444         }
445
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);
455                         PQclear(result);
456                 }
457         }
458
459         if (!intrans) {
460                 result = PQexec(dbconn, "COMMIT");
461                 PQclear(result);
462         }
463         
464         return 0;
465 }
466
467 /**
468  *      keyid2uid - Takes a keyid and returns the primary UID for it.
469  *      @keyid: The keyid to lookup.
470  */
471 static char *pg_keyid2uid(uint64_t keyid)
472 {
473         PGresult *result = NULL;
474         char statement[1024];
475         char *uid = NULL;
476
477         snprintf(statement, 1023,
478                 "SELECT uid FROM onak_uids WHERE keyid = '%llX' AND pri = 't'",
479                 keyid);
480         result = PQexec(dbconn, statement);
481
482         /*
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.
485          *
486          * TODO: Log if we get more than one response? Needs logging framework
487          * first though.
488          */
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.",
495                                 keyid);
496         }
497
498         PQclear(result);
499
500         return uid;
501 }
502
503 /**
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.
507  *
508  *      This function gets the list of signatures on a key. Used for key 
509  *      indexing and doing stats bits.
510  */
511 static struct ll *pg_getkeysigs(uint64_t keyid, bool *revoked)
512 {
513         struct ll *sigs = NULL;
514         PGresult *result = NULL;
515         uint64_t signer;
516         char statement[1024];
517         int i, j;
518         int numsigs = 0;
519         bool intrans = false;
520         char *str;
521
522         if (!intrans) {
523                 result = PQexec(dbconn, "BEGIN");
524                 PQclear(result);
525         }
526
527         snprintf(statement, 1023,
528                 "SELECT DISTINCT signer FROM onak_sigs WHERE signee = '%llX'",
529                 keyid);
530         result = PQexec(dbconn, statement);
531
532         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
533                 numsigs = PQntuples(result);
534                 for (i = 0; i < numsigs;  i++) {
535                         j = 0;
536                         signer = 0;
537                         str = PQgetvalue(result, i, 0);
538                         while (str[j] != 0) {
539                                 signer <<= 4;
540                                 if (str[j] >= '0' && str[j] <= '9') {
541                                         signer += str[j] - '0';
542                                 } else {
543                                         signer += str[j] - 'A' + 10;
544                                 }
545                                 j++;
546                         }
547                         sigs = lladd(sigs, createandaddtohash(signer));
548                 }
549         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
550                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
551         }
552
553         PQclear(result);
554
555         if (!intrans) {
556                 result = PQexec(dbconn, "COMMIT");
557                 PQclear(result);
558         }
559
560         /*
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.
564          */
565         if (revoked != NULL) {
566                 *revoked = false;
567         }
568         
569         return sigs;
570 }
571
572 /**
573  *      iterate_keys - call a function once for each key in the db.
574  *      @iterfunc: The function to call.
575  *      @ctx: A context pointer
576  *
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.
580  *
581  *      Returns the number of keys we iterated over.
582  */
583 static int pg_iterate_keys(void (*iterfunc)(void *ctx,
584                 struct openpgp_publickey *key), void *ctx)
585 {
586         struct openpgp_packet_list *packets = NULL;
587         struct openpgp_publickey *key = NULL;
588         PGresult *result = NULL;
589         char *oids = NULL;
590         char statement[1024];
591         int fd = -1;
592         int i = 0;
593         int numkeys = 0;
594         Oid key_oid;
595
596         result = PQexec(dbconn, "SELECT keydata FROM onak_keys;");
597
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);
603
604                         fd = lo_open(dbconn, key_oid, INV_READ);
605                         if (fd < 0) {
606                                 logthing(LOGTHING_ERROR,
607                                                 "Can't open large object.");
608                         } else {
609                                 read_openpgp_stream(keydb_fetchchar, &fd,
610                                                 &packets, 0);
611                                 parse_keys(packets, key);
612                                 lo_close(dbconn, fd);
613
614                                 iterfunc(ctx, key);
615                                         
616                                 free_publickey(key);
617                                 key = NULL;
618                                 free_packet_list(packets);
619                                 packets = NULL;
620                         }
621                 }
622         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
623                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
624         }
625
626         PQclear(result);
627
628         return (numkeys);
629 }
630
631 /*
632  * Include the basic keydb routines.
633  */
634 #define NEED_GETFULLKEYID 1
635 #define NEED_UPDATEKEYS 1
636 #include "keydb.c"
637
638 struct dbfuncs keydb_pg_funcs = {
639         .initdb                 = pg_initdb,
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,
653 };