]> git.sommitrealweird.co.uk Git - onak.git/blob - keydb_pg.c
8b89b7182115f296af3c37ca1bec7482c5a4d1a7
[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 <libpq-fe.h>
15 //#include <libpq/libpq-fs.h>
16 #include <sys/types.h>
17 #include <sys/uio.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #include "hash.h"
26 #include "keydb.h"
27 #include "keyid.h"
28 #include "decodekey.h"
29 #include "keystructs.h"
30 #include "log.h"
31 #include "mem.h"
32 #include "onak-conf.h"
33 #include "parsekey.h"
34
35 /**
36  *      dbconn - our connection to the database.
37  */
38 static PGconn *dbconn = NULL;
39
40 /**
41  *      keydb_fetchchar - Fetches a char from a file.
42  */
43 static int keydb_fetchchar(void *fd, size_t count, unsigned char *c)
44 {
45         return (!lo_read(dbconn, *(int *) fd, c, count));
46 }
47
48 /**
49  *      keydb_putchar - Puts a char to a file.
50  */
51 static int keydb_putchar(void *fd, size_t count, unsigned char *c)
52 {
53         return !(lo_write(dbconn, *(int *) fd, c, count));
54 }
55
56 /**
57  *      initdb - Initialize the key database.
58  *
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
61  *      for access.
62  */
63 void initdb(bool readonly)
64 {
65         dbconn = PQsetdbLogin(config.pg_dbhost, // host
66                         NULL, // port
67                         NULL, // options
68                         NULL, // tty
69                         config.pg_dbname, // database
70                         config.pg_dbuser,  //login
71                         config.pg_dbpass); // password
72
73         if (PQstatus(dbconn) == CONNECTION_BAD) {
74                 logthing(LOGTHING_CRITICAL, "Connection to database failed.");
75                 logthing(LOGTHING_CRITICAL, "%s", PQerrorMessage(dbconn));
76                 PQfinish(dbconn);
77                 dbconn = NULL;
78                 exit(1);
79         }
80 }
81
82 /**
83  *      cleanupdb - De-initialize the key database.
84  *
85  *      This function should be called upon program exit to allow the DB to
86  *      cleanup after itself.
87  */
88 void cleanupdb(void)
89 {
90         PQfinish(dbconn);
91         dbconn = NULL;
92 }
93
94 /**
95  *      starttrans - Start a transaction.
96  *
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.
100  */
101 bool starttrans(void)
102 {
103         PGresult *result = NULL;
104         
105         result = PQexec(dbconn, "BEGIN");
106         PQclear(result);
107
108         return true;
109 }
110
111 /**
112  *      endtrans - End a transaction.
113  *
114  *      Ends a transaction.
115  */
116 void endtrans(void)
117 {
118         PGresult *result = NULL;
119
120         result = PQexec(dbconn, "COMMIT");
121         PQclear(result);
122
123         return;
124 }
125
126 /**
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.
131  *
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
136  *      structure.
137  */
138 int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey,
139                 bool intrans)
140 {
141         struct openpgp_packet_list *packets = NULL;
142         PGresult *result = NULL;
143         char *oids = NULL;
144         char statement[1024];
145         int fd = -1;
146         int i = 0;
147         int numkeys = 0;
148         Oid key_oid;
149
150         if (!intrans) {
151                 result = PQexec(dbconn, "BEGIN");
152                 PQclear(result);
153         }
154         
155         if (keyid > 0xFFFFFFFF) {
156                 snprintf(statement, 1023,
157                         "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
158                         keyid);
159         } else {
160                 snprintf(statement, 1023,
161                         "SELECT keydata FROM onak_keys WHERE keyid "
162                         "LIKE '%%%llX'",
163                         keyid);
164         }
165         result = PQexec(dbconn, statement);
166
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);
172
173                         fd = lo_open(dbconn, key_oid, INV_READ);
174                         if (fd < 0) {
175                                 logthing(LOGTHING_ERROR,
176                                                 "Can't open large object.");
177                         } else {
178                                 read_openpgp_stream(keydb_fetchchar, &fd,
179                                                 &packets, 0);
180                                 parse_keys(packets, publickey);
181                                 lo_close(dbconn, fd);
182                                 free_packet_list(packets);
183                                 packets = NULL;
184                         }
185                 }
186         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
187                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
188         }
189
190         PQclear(result);
191
192         if (!intrans) {
193                 result = PQexec(dbconn, "COMMIT");
194                 PQclear(result);
195         }
196         return (numkeys);
197 }
198
199 /**
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.
203  *
204  *      This function searches for the supplied text and returns the keys that
205  *      contain it.
206  */
207 int fetch_key_text(const char *search, struct openpgp_publickey **publickey)
208 {
209         struct openpgp_packet_list *packets = NULL;
210         PGresult *result = NULL;
211         char *oids = NULL;
212         char statement[1024];
213         int fd = -1;
214         int i = 0;
215         int numkeys = 0;
216         Oid key_oid;
217         char *newsearch = NULL;
218
219         result = PQexec(dbconn, "BEGIN");
220         PQclear(result);
221
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%%'",
229                         newsearch);
230         result = PQexec(dbconn, statement);
231         free(newsearch);
232         newsearch = NULL;
233
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);
239
240                         fd = lo_open(dbconn, key_oid, INV_READ);
241                         if (fd < 0) {
242                                 logthing(LOGTHING_ERROR,
243                                                 "Can't open large object.");
244                         } else {
245                                 read_openpgp_stream(keydb_fetchchar, &fd,
246                                                 &packets);
247                                 parse_keys(packets, publickey);
248                                 lo_close(dbconn, fd);
249                                 free_packet_list(packets);
250                                 packets = NULL;
251                         }
252                 }
253         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
254                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
255         }
256
257         PQclear(result);
258
259         result = PQexec(dbconn, "COMMIT");
260         PQclear(result);
261         return (numkeys);
262 }
263
264 /**
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.
269  *
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.
275  */
276 int store_key(struct openpgp_publickey *publickey, bool intrans, bool update)
277 {
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];
284         Oid key_oid;
285         int fd;
286         char **uids = NULL;
287         char *primary = NULL;
288         char *safeuid = NULL;
289         int i;
290
291         if (!intrans) {
292                 result = PQexec(dbconn, "BEGIN");
293                 PQclear(result);
294         }
295
296         /*
297          * Delete the key if we already have it.
298          *
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.
303          */
304         if (update) {
305                 delete_key(get_keyid(publickey), true);
306         }
307
308         next = publickey->next;
309         publickey->next = NULL;
310         flatten_publickey(publickey, &packets, &list_end);
311         publickey->next = next;
312                 
313         key_oid = lo_creat(dbconn, INV_READ | INV_WRITE);
314         if (key_oid == 0) {
315                 logthing(LOGTHING_ERROR, "Can't create key OID");
316         } else {
317                 fd = lo_open(dbconn, key_oid, INV_WRITE);
318                 write_openpgp_stream(keydb_putchar, &fd, packets);
319                 lo_close(dbconn, fd);
320         }
321         free_packet_list(packets);
322         packets = NULL;
323
324         snprintf(statement, 1023, 
325                         "INSERT INTO onak_keys (keyid, keydata) VALUES "
326                         "('%llX', '%d')", 
327                         get_keyid(publickey),
328                         key_oid);
329         result = PQexec(dbconn, statement);
330
331         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
332                 logthing(LOGTHING_ERROR, "Problem storing key in DB.");
333                 logthing(LOGTHING_ERROR, "%s", PQresultErrorMessage(result));
334         }
335         PQclear(result);
336
337         uids = keyuids(publickey, &primary);
338         if (uids != NULL) {
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],
344                                                 strlen(uids[i]));
345
346                                 snprintf(statement, 1023,
347                                         "INSERT INTO onak_uids "
348                                         "(keyid, uid, pri) "
349                                         "VALUES ('%llX', '%s', '%c')",
350                                         get_keyid(publickey),
351                                         safeuid,
352                                         (uids[i] == primary) ? 't' : 'f');
353                                 result = PQexec(dbconn, statement);
354
355                                 free(safeuid);
356                                 safeuid = NULL;
357                         }
358                         if (uids[i] != NULL) {
359                                 free(uids[i]);
360                                 uids[i] = NULL;
361                         }
362
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));
368                         }
369                         /*
370                          * TODO: Check result.
371                          */
372                         PQclear(result);
373                 }
374                 free(uids);
375                 uids = NULL;
376         }
377
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);
387                         PQclear(result);
388                 }
389         }
390
391         if (!intrans) {
392                 result = PQexec(dbconn, "COMMIT");
393                 PQclear(result);
394         }
395         
396         return 0;
397 }
398
399 /**
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.
403  *
404  *      This function deletes a public key from whatever storage mechanism we
405  *      are using. Returns 0 if the key existed.
406  */
407 int delete_key(uint64_t keyid, bool intrans)
408 {
409         PGresult *result = NULL;
410         char *oids = NULL;
411         char statement[1024];
412         int found = 1;
413         int i;
414         Oid key_oid;
415
416         if (!intrans) {
417                 result = PQexec(dbconn, "BEGIN");
418                 PQclear(result);
419         }
420         
421         snprintf(statement, 1023,
422                         "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
423                         keyid);
424         result = PQexec(dbconn, statement);
425
426         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
427                 found = 0;
428                 i = PQntuples(result);
429                 while (i > 0) {
430                         oids = PQgetvalue(result, i-1, 0);
431                         key_oid = (Oid) atoi(oids);
432                         lo_unlink(dbconn, key_oid);
433                         i--;
434                 }
435                 PQclear(result);
436
437                 snprintf(statement, 1023,
438                         "DELETE FROM onak_keys WHERE keyid = '%llX'",
439                         keyid);
440                 result = PQexec(dbconn, statement);
441                 PQclear(result);
442
443                 snprintf(statement, 1023,
444                         "DELETE FROM onak_sigs WHERE signee = '%llX'",
445                         keyid);
446                 result = PQexec(dbconn, statement);
447                 PQclear(result);
448
449                 snprintf(statement, 1023,
450                         "DELETE FROM onak_uids WHERE keyid = '%llX'",
451                         keyid);
452                 result = PQexec(dbconn, statement);
453         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
454                 logthing(LOGTHING_ERROR,
455                                 "Problem retrieving key (%llX) from DB.",
456                                 keyid);
457         }
458
459         PQclear(result);
460
461         if (!intrans) {
462                 result = PQexec(dbconn, "COMMIT");
463                 PQclear(result);
464         }
465         return (found);
466 }
467
468 /**
469  *      keyid2uid - Takes a keyid and returns the primary UID for it.
470  *      @keyid: The keyid to lookup.
471  */
472 char *keyid2uid(uint64_t keyid)
473 {
474         PGresult *result = NULL;
475         char statement[1024];
476         char *uid = NULL;
477
478         snprintf(statement, 1023,
479                 "SELECT uid FROM onak_uids WHERE keyid = '%llX' AND pri = 't'",
480                 keyid);
481         result = PQexec(dbconn, statement);
482
483         /*
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.
486          *
487          * TODO: Log if we get more than one response? Needs logging framework
488          * first though.
489          */
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.",
496                                 keyid);
497         }
498
499         PQclear(result);
500
501         return uid;
502 }
503
504 /**
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.
508  *
509  *      This function gets the list of signatures on a key. Used for key 
510  *      indexing and doing stats bits.
511  */
512 struct ll *getkeysigs(uint64_t keyid, bool *revoked)
513 {
514         struct ll *sigs = NULL;
515         PGresult *result = NULL;
516         uint64_t signer;
517         char statement[1024];
518         int i, j;
519         int numsigs = 0;
520         bool intrans = false;
521         char *str;
522
523         if (!intrans) {
524                 result = PQexec(dbconn, "BEGIN");
525                 PQclear(result);
526         }
527
528         snprintf(statement, 1023,
529                 "SELECT DISTINCT signer FROM onak_sigs WHERE signee = '%llX'",
530                 keyid);
531         result = PQexec(dbconn, statement);
532
533         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
534                 numsigs = PQntuples(result);
535                 for (i = 0; i < numsigs;  i++) {
536                         j = 0;
537                         signer = 0;
538                         str = PQgetvalue(result, i, 0);
539                         while (str[j] != 0) {
540                                 signer <<= 4;
541                                 if (str[j] >= '0' && str[j] <= '9') {
542                                         signer += str[j] - '0';
543                                 } else {
544                                         signer += str[j] - 'A' + 10;
545                                 }
546                                 j++;
547                         }
548                         sigs = lladd(sigs, createandaddtohash(signer));
549                 }
550         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
551                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
552         }
553
554         PQclear(result);
555
556         if (!intrans) {
557                 result = PQexec(dbconn, "COMMIT");
558                 PQclear(result);
559         }
560
561         /*
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.
565          */
566         if (revoked != NULL) {
567                 *revoked = false;
568         }
569         
570         return sigs;
571 }
572
573 /**
574  *      dumpdb - dump the key database
575  *      @filenamebase: The base filename to use for the dump.
576  *
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
581  *      extension.
582  *          */
583 int dumpdb(char *filenamebase)
584 {
585         return 0;
586 }
587
588 /*
589  * Include the basic keydb routines.
590  */
591 #define NEED_GETFULLKEYID 1
592 #include "keydb.c"