]> git.sommitrealweird.co.uk Git - onak.git/blob - keydb_pg.c
8d974fd6d0f4122c334b3c74d0ea2ebf6235fda3
[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 Project Purple
7  *
8  * $Id: keydb_pg.c,v 1.14 2004/03/23 12:33:47 noodles Exp $
9  */
10
11 #include <postgresql/libpq-fe.h>
12 #include <postgresql/libpq/libpq-fs.h>
13
14 #include <sys/types.h>
15 #include <sys/uio.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22
23 #include "hash.h"
24 #include "keydb.h"
25 #include "keyid.h"
26 #include "decodekey.h"
27 #include "keystructs.h"
28 #include "log.h"
29 #include "mem.h"
30 #include "onak-conf.h"
31 #include "parsekey.h"
32
33 /**
34  *      dbconn - our connection to the database.
35  */
36 static PGconn *dbconn = NULL;
37
38 /**
39  *      keydb_fetchchar - Fetches a char from a file.
40  */
41 static int keydb_fetchchar(void *fd, size_t count, unsigned char *c)
42 {
43         return (!lo_read(dbconn, *(int *) fd, (char *) c, count));
44 }
45
46 /**
47  *      keydb_putchar - Puts a char to a file.
48  */
49 static int keydb_putchar(void *fd, size_t count, unsigned char *c)
50 {
51         return !(lo_write(dbconn, *(int *) fd, (char *) c, count));
52 }
53
54 /**
55  *      initdb - Initialize the key database.
56  *
57  *      This function should be called before any of the other functions in
58  *      this file are called in order to allow the DB to be initialized ready
59  *      for access.
60  */
61 void initdb(bool readonly)
62 {
63         dbconn = PQsetdbLogin(config.pg_dbhost, // host
64                         NULL, // port
65                         NULL, // options
66                         NULL, // tty
67                         config.pg_dbname, // database
68                         config.pg_dbuser,  //login
69                         config.pg_dbpass); // password
70
71         if (PQstatus(dbconn) == CONNECTION_BAD) {
72                 logthing(LOGTHING_CRITICAL, "Connection to database failed.");
73                 logthing(LOGTHING_CRITICAL, "%s", PQerrorMessage(dbconn));
74                 PQfinish(dbconn);
75                 dbconn = NULL;
76                 exit(1);
77         }
78 }
79
80 /**
81  *      cleanupdb - De-initialize the key database.
82  *
83  *      This function should be called upon program exit to allow the DB to
84  *      cleanup after itself.
85  */
86 void cleanupdb(void)
87 {
88         PQfinish(dbconn);
89         dbconn = NULL;
90 }
91
92 /**
93  *      starttrans - Start a transaction.
94  *
95  *      Start a transaction. Intended to be used if we're about to perform many
96  *      operations on the database to help speed it all up, or if we want
97  *      something to only succeed if all relevant operations are successful.
98  */
99 bool starttrans(void)
100 {
101         PGresult *result = NULL;
102         
103         result = PQexec(dbconn, "BEGIN");
104         PQclear(result);
105
106         return true;
107 }
108
109 /**
110  *      endtrans - End a transaction.
111  *
112  *      Ends a transaction.
113  */
114 void endtrans(void)
115 {
116         PGresult *result = NULL;
117
118         result = PQexec(dbconn, "COMMIT");
119         PQclear(result);
120
121         return;
122 }
123
124 /**
125  *      fetch_key - Given a keyid fetch the key from storage.
126  *      @keyid: The keyid to fetch.
127  *      @publickey: A pointer to a structure to return the key in.
128  *      @intrans: If we're already in a transaction.
129  *
130  *      We use the hex representation of the keyid as the filename to fetch the
131  *      key from. The key is stored in the file as a binary OpenPGP stream of
132  *      packets, so we can just use read_openpgp_stream() to read the packets
133  *      in and then parse_keys() to parse the packets into a publickey
134  *      structure.
135  */
136 int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey,
137                 bool intrans)
138 {
139         struct openpgp_packet_list *packets = NULL;
140         PGresult *result = NULL;
141         char *oids = NULL;
142         char statement[1024];
143         int fd = -1;
144         int i = 0;
145         int numkeys = 0;
146         Oid key_oid;
147
148         if (!intrans) {
149                 result = PQexec(dbconn, "BEGIN");
150                 PQclear(result);
151         }
152         
153         if (keyid > 0xFFFFFFFF) {
154                 snprintf(statement, 1023,
155                         "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
156                         keyid);
157         } else {
158                 snprintf(statement, 1023,
159                         "SELECT keydata FROM onak_keys WHERE keyid "
160                         "LIKE '%%%llX'",
161                         keyid);
162         }
163         result = PQexec(dbconn, statement);
164
165         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
166                 numkeys = PQntuples(result);
167                 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
168                         oids = PQgetvalue(result, i, 0);
169                         key_oid = (Oid) atoi(oids);
170
171                         fd = lo_open(dbconn, key_oid, INV_READ);
172                         if (fd < 0) {
173                                 logthing(LOGTHING_ERROR,
174                                                 "Can't open large object.");
175                         } else {
176                                 read_openpgp_stream(keydb_fetchchar, &fd,
177                                                 &packets, 0);
178                                 parse_keys(packets, publickey);
179                                 lo_close(dbconn, fd);
180                                 free_packet_list(packets);
181                                 packets = NULL;
182                         }
183                 }
184         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
185                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
186         }
187
188         PQclear(result);
189
190         if (!intrans) {
191                 result = PQexec(dbconn, "COMMIT");
192                 PQclear(result);
193         }
194         return (numkeys);
195 }
196
197 /**
198  *      fetch_key_text - Trys to find the keys that contain the supplied text.
199  *      @search: The text to search for.
200  *      @publickey: A pointer to a structure to return the key in.
201  *
202  *      This function searches for the supplied text and returns the keys that
203  *      contain it.
204  */
205 int fetch_key_text(const char *search, struct openpgp_publickey **publickey)
206 {
207         struct openpgp_packet_list *packets = NULL;
208         PGresult *result = NULL;
209         char *oids = NULL;
210         char statement[1024];
211         int fd = -1;
212         int i = 0;
213         int numkeys = 0;
214         Oid key_oid;
215         char *newsearch = NULL;
216
217         result = PQexec(dbconn, "BEGIN");
218         PQclear(result);
219
220         newsearch = malloc(strlen(search) * 2 + 1);
221         memset(newsearch, 0, strlen(search) * 2 + 1);
222         PQescapeString(newsearch, search, strlen(search));
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%%'",
227                         newsearch);
228         result = PQexec(dbconn, statement);
229         free(newsearch);
230         newsearch = NULL;
231
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);
237
238                         fd = lo_open(dbconn, key_oid, INV_READ);
239                         if (fd < 0) {
240                                 logthing(LOGTHING_ERROR,
241                                                 "Can't open large object.");
242                         } else {
243                                 read_openpgp_stream(keydb_fetchchar, &fd,
244                                                 &packets,
245                                                 0);
246                                 parse_keys(packets, publickey);
247                                 lo_close(dbconn, fd);
248                                 free_packet_list(packets);
249                                 packets = NULL;
250                         }
251                 }
252         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
253                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
254         }
255
256         PQclear(result);
257
258         result = PQexec(dbconn, "COMMIT");
259         PQclear(result);
260         return (numkeys);
261 }
262
263 /**
264  *      store_key - Takes a key and stores it.
265  *      @publickey: A pointer to the public key to store.
266  *      @intrans: If we're already in a transaction.
267  *      @update: If true the key exists and should be updated.
268  *
269  *      Again we just use the hex representation of the keyid as the filename
270  *      to store the key to. We flatten the public key to a list of OpenPGP
271  *      packets and then use write_openpgp_stream() to write the stream out to
272  *      the file. If update is true then we delete the old key first, otherwise
273  *      we trust that it doesn't exist.
274  */
275 int store_key(struct openpgp_publickey *publickey, bool intrans, bool update)
276 {
277         struct openpgp_packet_list *packets = NULL;
278         struct openpgp_packet_list *list_end = NULL;
279         struct openpgp_publickey *next = NULL;
280         struct openpgp_signedpacket_list *curuid = NULL;
281         PGresult *result = NULL;
282         char statement[1024];
283         Oid key_oid;
284         int fd;
285         char **uids = NULL;
286         char *primary = NULL;
287         char *safeuid = NULL;
288         int i;
289
290         if (!intrans) {
291                 result = PQexec(dbconn, "BEGIN");
292                 PQclear(result);
293         }
294
295         /*
296          * Delete the key if we already have it.
297          *
298          * TODO: Can we optimize this perhaps? Possibly when other data is
299          * involved as well? I suspect this is easiest and doesn't make a lot
300          * of difference though - the largest chunk of data is the keydata and
301          * it definitely needs updated.
302          */
303         if (update) {
304                 delete_key(get_keyid(publickey), true);
305         }
306
307         next = publickey->next;
308         publickey->next = NULL;
309         flatten_publickey(publickey, &packets, &list_end);
310         publickey->next = next;
311                 
312         key_oid = lo_creat(dbconn, INV_READ | INV_WRITE);
313         if (key_oid == 0) {
314                 logthing(LOGTHING_ERROR, "Can't create key OID");
315         } else {
316                 fd = lo_open(dbconn, key_oid, INV_WRITE);
317                 write_openpgp_stream(keydb_putchar, &fd, packets);
318                 lo_close(dbconn, fd);
319         }
320         free_packet_list(packets);
321         packets = NULL;
322
323         snprintf(statement, 1023, 
324                         "INSERT INTO onak_keys (keyid, keydata) VALUES "
325                         "('%llX', '%d')", 
326                         get_keyid(publickey),
327                         key_oid);
328         result = PQexec(dbconn, statement);
329
330         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
331                 logthing(LOGTHING_ERROR, "Problem storing key in DB.");
332                 logthing(LOGTHING_ERROR, "%s", PQresultErrorMessage(result));
333         }
334         PQclear(result);
335
336         uids = keyuids(publickey, &primary);
337         if (uids != NULL) {
338                 for (i = 0; uids[i] != NULL; i++) {
339                         safeuid = malloc(strlen(uids[i]) * 2 + 1);
340                         if (safeuid != NULL) {
341                                 memset(safeuid, 0, strlen(uids[i]) * 2 + 1);
342                                 PQescapeString(safeuid, uids[i],
343                                                 strlen(uids[i]));
344
345                                 snprintf(statement, 1023,
346                                         "INSERT INTO onak_uids "
347                                         "(keyid, uid, pri) "
348                                         "VALUES ('%llX', '%s', '%c')",
349                                         get_keyid(publickey),
350                                         safeuid,
351                                         (uids[i] == primary) ? 't' : 'f');
352                                 result = PQexec(dbconn, statement);
353
354                                 free(safeuid);
355                                 safeuid = NULL;
356                         }
357                         if (uids[i] != NULL) {
358                                 free(uids[i]);
359                                 uids[i] = NULL;
360                         }
361
362                         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
363                                 logthing(LOGTHING_ERROR,
364                                                 "Problem storing key in DB.");
365                                 logthing(LOGTHING_ERROR, "%s",
366                                                 PQresultErrorMessage(result));
367                         }
368                         /*
369                          * TODO: Check result.
370                          */
371                         PQclear(result);
372                 }
373                 free(uids);
374                 uids = NULL;
375         }
376
377         for (curuid = publickey->uids; curuid != NULL; curuid = curuid->next) {
378                 for (packets = curuid->sigs; packets != NULL; 
379                                 packets = packets->next) {
380                         snprintf(statement, 1023,
381                                 "INSERT INTO onak_sigs (signer, signee) "
382                                 "VALUES ('%llX', '%llX')",
383                                 sig_keyid(packets->packet),
384                                 get_keyid(publickey));
385                         result = PQexec(dbconn, statement);
386                         PQclear(result);
387                 }
388         }
389
390         if (!intrans) {
391                 result = PQexec(dbconn, "COMMIT");
392                 PQclear(result);
393         }
394         
395         return 0;
396 }
397
398 /**
399  *      delete_key - Given a keyid delete the key from storage.
400  *      @keyid: The keyid to delete.
401  *      @intrans: If we're already in a transaction.
402  *
403  *      This function deletes a public key from whatever storage mechanism we
404  *      are using. Returns 0 if the key existed.
405  */
406 int delete_key(uint64_t keyid, bool intrans)
407 {
408         PGresult *result = NULL;
409         char *oids = NULL;
410         char statement[1024];
411         int found = 1;
412         int i;
413         Oid key_oid;
414
415         if (!intrans) {
416                 result = PQexec(dbconn, "BEGIN");
417                 PQclear(result);
418         }
419         
420         snprintf(statement, 1023,
421                         "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
422                         keyid);
423         result = PQexec(dbconn, statement);
424
425         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
426                 found = 0;
427                 i = PQntuples(result);
428                 while (i > 0) {
429                         oids = PQgetvalue(result, i-1, 0);
430                         key_oid = (Oid) atoi(oids);
431                         lo_unlink(dbconn, key_oid);
432                         i--;
433                 }
434                 PQclear(result);
435
436                 snprintf(statement, 1023,
437                         "DELETE FROM onak_keys WHERE keyid = '%llX'",
438                         keyid);
439                 result = PQexec(dbconn, statement);
440                 PQclear(result);
441
442                 snprintf(statement, 1023,
443                         "DELETE FROM onak_sigs WHERE signee = '%llX'",
444                         keyid);
445                 result = PQexec(dbconn, statement);
446                 PQclear(result);
447
448                 snprintf(statement, 1023,
449                         "DELETE FROM onak_uids WHERE keyid = '%llX'",
450                         keyid);
451                 result = PQexec(dbconn, statement);
452         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
453                 logthing(LOGTHING_ERROR,
454                                 "Problem retrieving key (%llX) from DB.",
455                                 keyid);
456         }
457
458         PQclear(result);
459
460         if (!intrans) {
461                 result = PQexec(dbconn, "COMMIT");
462                 PQclear(result);
463         }
464         return (found);
465 }
466
467 /**
468  *      keyid2uid - Takes a keyid and returns the primary UID for it.
469  *      @keyid: The keyid to lookup.
470  */
471 char *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 struct ll *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  *      dumpdb - dump the key database
574  *      @filenamebase: The base filename to use for the dump.
575  *
576  *      Dumps the database into one or more files, which contain pure OpenPGP
577  *      that can be reimported into onak or gpg. filenamebase provides a base
578  *      file name for the dump; several files may be created, all of which will
579  *      begin with this string and then have a unique number and a .pgp
580  *      extension.
581  *          */
582 int dumpdb(char *filenamebase)
583 {
584         return 0;
585 }
586
587 /*
588  * Include the basic keydb routines.
589  */
590 #define NEED_GETFULLKEYID 1
591 #include "keydb.c"