]> git.sommitrealweird.co.uk Git - onak.git/blob - keydb_pg.c
Prevent read_openpgp_stream from returning empty packets
[onak.git] / keydb_pg.c
1 /*
2  * keydb_pg.c - Routines to store and fetch keys in a PostGres database.
3  *
4  * Copyright 2002-2004 Jonathan McDowell <noodles@earth.li>
5  *
6  * This program is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the Free
8  * Software Foundation; version 2 of the License.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13  * more details.
14  *
15  * You should have received a copy of the GNU General Public License along with
16  * this program; if not, write to the Free Software Foundation, Inc., 51
17  * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #include <postgresql/libpq-fe.h>
21 #include <postgresql/libpq/libpq-fs.h>
22
23 #include <sys/types.h>
24 #include <sys/uio.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #include "hash.h"
33 #include "keydb.h"
34 #include "keyid.h"
35 #include "decodekey.h"
36 #include "keystructs.h"
37 #include "log.h"
38 #include "mem.h"
39 #include "onak-conf.h"
40 #include "parsekey.h"
41
42 /**
43  *      dbconn - our connection to the database.
44  */
45 static PGconn *dbconn = NULL;
46
47 /**
48  *      keydb_fetchchar - Fetches a char from a file.
49  */
50 static int keydb_fetchchar(void *fd, size_t count, void *c)
51 {
52         return (!lo_read(dbconn, *(int *) fd, (char *) c, count));
53 }
54
55 /**
56  *      keydb_putchar - Puts a char to a file.
57  */
58 static int keydb_putchar(void *fd, size_t count, void *c)
59 {
60         return !(lo_write(dbconn, *(int *) fd, (char *) c, count));
61 }
62
63 /**
64  *      initdb - Initialize the key database.
65  *
66  *      This function should be called before any of the other functions in
67  *      this file are called in order to allow the DB to be initialized ready
68  *      for access.
69  */
70 static void pg_initdb(bool readonly)
71 {
72         dbconn = PQsetdbLogin(config.pg_dbhost, // host
73                         NULL, // port
74                         NULL, // options
75                         NULL, // tty
76                         config.pg_dbname, // database
77                         config.pg_dbuser,  //login
78                         config.pg_dbpass); // password
79
80         if (PQstatus(dbconn) == CONNECTION_BAD) {
81                 logthing(LOGTHING_CRITICAL, "Connection to database failed.");
82                 logthing(LOGTHING_CRITICAL, "%s", PQerrorMessage(dbconn));
83                 PQfinish(dbconn);
84                 dbconn = NULL;
85                 exit(1);
86         }
87 }
88
89 /**
90  *      cleanupdb - De-initialize the key database.
91  *
92  *      This function should be called upon program exit to allow the DB to
93  *      cleanup after itself.
94  */
95 static void pg_cleanupdb(void)
96 {
97         PQfinish(dbconn);
98         dbconn = NULL;
99 }
100
101 /**
102  *      starttrans - Start a transaction.
103  *
104  *      Start a transaction. Intended to be used if we're about to perform many
105  *      operations on the database to help speed it all up, or if we want
106  *      something to only succeed if all relevant operations are successful.
107  */
108 static bool pg_starttrans(void)
109 {
110         PGresult *result = NULL;
111         
112         result = PQexec(dbconn, "BEGIN");
113         PQclear(result);
114
115         return true;
116 }
117
118 /**
119  *      endtrans - End a transaction.
120  *
121  *      Ends a transaction.
122  */
123 static void pg_endtrans(void)
124 {
125         PGresult *result = NULL;
126
127         result = PQexec(dbconn, "COMMIT");
128         PQclear(result);
129
130         return;
131 }
132
133 /**
134  *      fetch_key - Given a keyid fetch the key from storage.
135  *      @keyid: The keyid to fetch.
136  *      @publickey: A pointer to a structure to return the key in.
137  *      @intrans: If we're already in a transaction.
138  *
139  *      We use the hex representation of the keyid as the filename to fetch the
140  *      key from. The key is stored in the file as a binary OpenPGP stream of
141  *      packets, so we can just use read_openpgp_stream() to read the packets
142  *      in and then parse_keys() to parse the packets into a publickey
143  *      structure.
144  */
145 static int pg_fetch_key(uint64_t keyid, struct openpgp_publickey **publickey,
146                 bool intrans)
147 {
148         struct openpgp_packet_list *packets = NULL;
149         PGresult *result = NULL;
150         char *oids = NULL;
151         char statement[1024];
152         int fd = -1;
153         int i = 0;
154         int numkeys = 0;
155         Oid key_oid;
156
157         if (!intrans) {
158                 result = PQexec(dbconn, "BEGIN");
159                 PQclear(result);
160         }
161         
162         if (keyid > 0xFFFFFFFF) {
163                 snprintf(statement, 1023,
164                         "SELECT keydata FROM onak_keys WHERE keyid = '%"
165                         PRIX64 "'",
166                         keyid);
167         } else {
168                 snprintf(statement, 1023,
169                         "SELECT keydata FROM onak_keys WHERE keyid "
170                         "LIKE '%%%" PRIX64 "'",
171                         keyid);
172         }
173         result = PQexec(dbconn, statement);
174
175         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
176                 numkeys = PQntuples(result);
177                 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
178                         oids = PQgetvalue(result, i, 0);
179                         key_oid = (Oid) atoi(oids);
180
181                         fd = lo_open(dbconn, key_oid, INV_READ);
182                         if (fd < 0) {
183                                 logthing(LOGTHING_ERROR,
184                                                 "Can't open large object.");
185                         } else {
186                                 read_openpgp_stream(keydb_fetchchar, &fd,
187                                                 &packets, 0);
188                                 parse_keys(packets, publickey);
189                                 lo_close(dbconn, fd);
190                                 free_packet_list(packets);
191                                 packets = NULL;
192                         }
193                 }
194         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
195                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
196         }
197
198         PQclear(result);
199
200         if (!intrans) {
201                 result = PQexec(dbconn, "COMMIT");
202                 PQclear(result);
203         }
204         return (numkeys);
205 }
206
207 /**
208  *      fetch_key_text - Trys to find the keys that contain the supplied text.
209  *      @search: The text to search for.
210  *      @publickey: A pointer to a structure to return the key in.
211  *
212  *      This function searches for the supplied text and returns the keys that
213  *      contain it.
214  */
215 static int pg_fetch_key_text(const char *search,
216                 struct openpgp_publickey **publickey)
217 {
218         struct openpgp_packet_list *packets = NULL;
219         PGresult *result = NULL;
220         char *oids = NULL;
221         char statement[1024];
222         int fd = -1;
223         int i = 0;
224         int numkeys = 0;
225         Oid key_oid;
226         char *newsearch = NULL;
227
228         result = PQexec(dbconn, "BEGIN");
229         PQclear(result);
230
231         newsearch = malloc(strlen(search) * 2 + 1);
232         memset(newsearch, 0, strlen(search) * 2 + 1);
233         PQescapeStringConn(dbconn, newsearch, search, strlen(search), NULL);
234         snprintf(statement, 1023,
235                         "SELECT DISTINCT onak_keys.keydata FROM onak_keys, "
236                         "onak_uids WHERE onak_keys.keyid = onak_uids.keyid "
237                         "AND onak_uids.uid LIKE '%%%s%%'",
238                         newsearch);
239         result = PQexec(dbconn, statement);
240         free(newsearch);
241         newsearch = NULL;
242
243         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
244                 numkeys = PQntuples(result);
245                 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
246                         oids = PQgetvalue(result, i, 0);
247                         key_oid = (Oid) atoi(oids);
248
249                         fd = lo_open(dbconn, key_oid, INV_READ);
250                         if (fd < 0) {
251                                 logthing(LOGTHING_ERROR,
252                                                 "Can't open large object.");
253                         } else {
254                                 read_openpgp_stream(keydb_fetchchar, &fd,
255                                                 &packets,
256                                                 0);
257                                 parse_keys(packets, publickey);
258                                 lo_close(dbconn, fd);
259                                 free_packet_list(packets);
260                                 packets = NULL;
261                         }
262                 }
263         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
264                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
265         }
266
267         PQclear(result);
268
269         result = PQexec(dbconn, "COMMIT");
270         PQclear(result);
271         return (numkeys);
272 }
273
274 /**
275  *      delete_key - Given a keyid delete the key from storage.
276  *      @keyid: The keyid to delete.
277  *      @intrans: If we're already in a transaction.
278  *
279  *      This function deletes a public key from whatever storage mechanism we
280  *      are using. Returns 0 if the key existed.
281  */
282 static int pg_delete_key(uint64_t keyid, bool intrans)
283 {
284         PGresult *result = NULL;
285         char *oids = NULL;
286         char statement[1024];
287         int found = 1;
288         int i;
289         Oid key_oid;
290
291         if (!intrans) {
292                 result = PQexec(dbconn, "BEGIN");
293                 PQclear(result);
294         }
295         
296         snprintf(statement, 1023,
297                         "SELECT keydata FROM onak_keys WHERE keyid = '%"
298                         PRIX64 "'",
299                         keyid);
300         result = PQexec(dbconn, statement);
301
302         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
303                 found = 0;
304                 i = PQntuples(result);
305                 while (i > 0) {
306                         oids = PQgetvalue(result, i-1, 0);
307                         key_oid = (Oid) atoi(oids);
308                         lo_unlink(dbconn, key_oid);
309                         i--;
310                 }
311                 PQclear(result);
312
313                 snprintf(statement, 1023,
314                         "DELETE FROM onak_keys WHERE keyid = '%" PRIX64 "'",
315                         keyid);
316                 result = PQexec(dbconn, statement);
317                 PQclear(result);
318
319                 snprintf(statement, 1023,
320                         "DELETE FROM onak_sigs WHERE signee = '%" PRIX64 "'",
321                         keyid);
322                 result = PQexec(dbconn, statement);
323                 PQclear(result);
324
325                 snprintf(statement, 1023,
326                         "DELETE FROM onak_uids WHERE keyid = '%" PRIX64 "'",
327                         keyid);
328                 result = PQexec(dbconn, statement);
329         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
330                 logthing(LOGTHING_ERROR,
331                                 "Problem retrieving key (%" PRIX64
332                                 ") from DB.",
333                                 keyid);
334         }
335
336         PQclear(result);
337
338         if (!intrans) {
339                 result = PQexec(dbconn, "COMMIT");
340                 PQclear(result);
341         }
342         return (found);
343 }
344
345 /**
346  *      store_key - Takes a key and stores it.
347  *      @publickey: A pointer to the public key to store.
348  *      @intrans: If we're already in a transaction.
349  *      @update: If true the key exists and should be updated.
350  *
351  *      Again we just use the hex representation of the keyid as the filename
352  *      to store the key to. We flatten the public key to a list of OpenPGP
353  *      packets and then use write_openpgp_stream() to write the stream out to
354  *      the file. If update is true then we delete the old key first, otherwise
355  *      we trust that it doesn't exist.
356  */
357 static int pg_store_key(struct openpgp_publickey *publickey, bool intrans,
358                 bool update)
359 {
360         struct openpgp_packet_list *packets = NULL;
361         struct openpgp_packet_list *list_end = NULL;
362         struct openpgp_publickey *next = NULL;
363         struct openpgp_signedpacket_list *curuid = NULL;
364         PGresult *result = NULL;
365         char statement[1024];
366         Oid key_oid;
367         int fd;
368         char **uids = NULL;
369         char *primary = NULL;
370         char *safeuid = NULL;
371         int i;
372
373         if (!intrans) {
374                 result = PQexec(dbconn, "BEGIN");
375                 PQclear(result);
376         }
377
378         /*
379          * Delete the key if we already have it.
380          *
381          * TODO: Can we optimize this perhaps? Possibly when other data is
382          * involved as well? I suspect this is easiest and doesn't make a lot
383          * of difference though - the largest chunk of data is the keydata and
384          * it definitely needs updated.
385          */
386         if (update) {
387                 pg_delete_key(get_keyid(publickey), true);
388         }
389
390         next = publickey->next;
391         publickey->next = NULL;
392         flatten_publickey(publickey, &packets, &list_end);
393         publickey->next = next;
394                 
395         key_oid = lo_creat(dbconn, INV_READ | INV_WRITE);
396         if (key_oid == 0) {
397                 logthing(LOGTHING_ERROR, "Can't create key OID");
398         } else {
399                 fd = lo_open(dbconn, key_oid, INV_WRITE);
400                 write_openpgp_stream(keydb_putchar, &fd, packets);
401                 lo_close(dbconn, fd);
402         }
403         free_packet_list(packets);
404         packets = NULL;
405
406         snprintf(statement, 1023, 
407                         "INSERT INTO onak_keys (keyid, keydata) VALUES "
408                         "('%" PRIX64 "', '%d')", 
409                         get_keyid(publickey),
410                         key_oid);
411         result = PQexec(dbconn, statement);
412
413         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
414                 logthing(LOGTHING_ERROR, "Problem storing key in DB.");
415                 logthing(LOGTHING_ERROR, "%s", PQresultErrorMessage(result));
416         }
417         PQclear(result);
418
419         uids = keyuids(publickey, &primary);
420         if (uids != NULL) {
421                 for (i = 0; uids[i] != NULL; i++) {
422                         safeuid = malloc(strlen(uids[i]) * 2 + 1);
423                         if (safeuid != NULL) {
424                                 memset(safeuid, 0, strlen(uids[i]) * 2 + 1);
425                                 PQescapeStringConn(dbconn, safeuid, uids[i],
426                                                 strlen(uids[i]), NULL);
427
428                                 snprintf(statement, 1023,
429                                         "INSERT INTO onak_uids "
430                                         "(keyid, uid, pri) "
431                                         "VALUES ('%" PRIX64 "', '%s', '%c')",
432                                         get_keyid(publickey),
433                                         safeuid,
434                                         (uids[i] == primary) ? 't' : 'f');
435                                 result = PQexec(dbconn, statement);
436
437                                 free(safeuid);
438                                 safeuid = NULL;
439                         }
440                         if (uids[i] != NULL) {
441                                 free(uids[i]);
442                                 uids[i] = NULL;
443                         }
444
445                         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
446                                 logthing(LOGTHING_ERROR,
447                                                 "Problem storing key in DB.");
448                                 logthing(LOGTHING_ERROR, "%s",
449                                                 PQresultErrorMessage(result));
450                         }
451                         /*
452                          * TODO: Check result.
453                          */
454                         PQclear(result);
455                 }
456                 free(uids);
457                 uids = NULL;
458         }
459
460         for (curuid = publickey->uids; curuid != NULL; curuid = curuid->next) {
461                 for (packets = curuid->sigs; packets != NULL; 
462                                 packets = packets->next) {
463                         snprintf(statement, 1023,
464                                 "INSERT INTO onak_sigs (signer, signee) "
465                                 "VALUES ('%" PRIX64 "', '%" PRIX64 "')",
466                                 sig_keyid(packets->packet),
467                                 get_keyid(publickey));
468                         result = PQexec(dbconn, statement);
469                         PQclear(result);
470                 }
471         }
472
473         if (!intrans) {
474                 result = PQexec(dbconn, "COMMIT");
475                 PQclear(result);
476         }
477         
478         return 0;
479 }
480
481 /**
482  *      keyid2uid - Takes a keyid and returns the primary UID for it.
483  *      @keyid: The keyid to lookup.
484  */
485 static char *pg_keyid2uid(uint64_t keyid)
486 {
487         PGresult *result = NULL;
488         char statement[1024];
489         char *uid = NULL;
490
491         snprintf(statement, 1023,
492                 "SELECT uid FROM onak_uids WHERE keyid = '%" PRIX64
493                 "' AND pri = 't'",
494                 keyid);
495         result = PQexec(dbconn, statement);
496
497         /*
498          * Technically we only expect one response to the query; a key only has
499          * one primary ID. Better to return something than nothing though.
500          *
501          * TODO: Log if we get more than one response? Needs logging framework
502          * first though.
503          */
504         if (PQresultStatus(result) == PGRES_TUPLES_OK &&
505                         PQntuples(result) >= 1) {
506                 uid = strdup(PQgetvalue(result, 0, 0));
507         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
508                 logthing(LOGTHING_ERROR,
509                                 "Problem retrieving key (%" PRIX64
510                                 ") from DB.",
511                                 keyid);
512         }
513
514         PQclear(result);
515
516         return uid;
517 }
518
519 /**
520  *      getkeysigs - Gets a linked list of the signatures on a key.
521  *      @keyid: The keyid to get the sigs for.
522  *      @revoked: If the key is revoked.
523  *
524  *      This function gets the list of signatures on a key. Used for key 
525  *      indexing and doing stats bits.
526  */
527 static struct ll *pg_getkeysigs(uint64_t keyid, bool *revoked)
528 {
529         struct ll *sigs = NULL;
530         PGresult *result = NULL;
531         uint64_t signer;
532         char statement[1024];
533         int i, j;
534         int numsigs = 0;
535         bool intrans = false;
536         char *str;
537
538         if (!intrans) {
539                 result = PQexec(dbconn, "BEGIN");
540                 PQclear(result);
541         }
542
543         snprintf(statement, 1023,
544                 "SELECT DISTINCT signer FROM onak_sigs WHERE signee = '%"
545                 PRIX64 "'",
546                 keyid);
547         result = PQexec(dbconn, statement);
548
549         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
550                 numsigs = PQntuples(result);
551                 for (i = 0; i < numsigs;  i++) {
552                         j = 0;
553                         signer = 0;
554                         str = PQgetvalue(result, i, 0);
555                         while (str[j] != 0) {
556                                 signer <<= 4;
557                                 if (str[j] >= '0' && str[j] <= '9') {
558                                         signer += str[j] - '0';
559                                 } else {
560                                         signer += str[j] - 'A' + 10;
561                                 }
562                                 j++;
563                         }
564                         sigs = lladd(sigs, createandaddtohash(signer));
565                 }
566         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
567                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
568         }
569
570         PQclear(result);
571
572         if (!intrans) {
573                 result = PQexec(dbconn, "COMMIT");
574                 PQclear(result);
575         }
576
577         /*
578          * TODO: What do we do about revocations? We don't have the details
579          * stored in a separate table, so we'd have to grab the key and decode
580          * it, which we're trying to avoid by having a signers table.
581          */
582         if (revoked != NULL) {
583                 *revoked = false;
584         }
585         
586         return sigs;
587 }
588
589 /**
590  *      iterate_keys - call a function once for each key in the db.
591  *      @iterfunc: The function to call.
592  *      @ctx: A context pointer
593  *
594  *      Calls iterfunc once for each key in the database. ctx is passed
595  *      unaltered to iterfunc. This function is intended to aid database dumps
596  *      and statistic calculations.
597  *
598  *      Returns the number of keys we iterated over.
599  */
600 static int pg_iterate_keys(void (*iterfunc)(void *ctx,
601                 struct openpgp_publickey *key), void *ctx)
602 {
603         struct openpgp_packet_list *packets = NULL;
604         struct openpgp_publickey *key = NULL;
605         PGresult *result = NULL;
606         char *oids = NULL;
607         int fd = -1;
608         int i = 0;
609         int numkeys = 0;
610         Oid key_oid;
611
612         result = PQexec(dbconn, "SELECT keydata FROM onak_keys;");
613
614         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
615                 numkeys = PQntuples(result);
616                 for (i = 0; i < numkeys; i++) {
617                         oids = PQgetvalue(result, i, 0);
618                         key_oid = (Oid) atoi(oids);
619
620                         fd = lo_open(dbconn, key_oid, INV_READ);
621                         if (fd < 0) {
622                                 logthing(LOGTHING_ERROR,
623                                                 "Can't open large object.");
624                         } else {
625                                 read_openpgp_stream(keydb_fetchchar, &fd,
626                                                 &packets, 0);
627                                 parse_keys(packets, &key);
628                                 lo_close(dbconn, fd);
629
630                                 iterfunc(ctx, key);
631                                         
632                                 free_publickey(key);
633                                 key = NULL;
634                                 free_packet_list(packets);
635                                 packets = NULL;
636                         }
637                 }
638         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
639                 logthing(LOGTHING_ERROR, "Problem retrieving key from DB.");
640         }
641
642         PQclear(result);
643
644         return (numkeys);
645 }
646
647 /*
648  * Include the basic keydb routines.
649  */
650 #define NEED_GETFULLKEYID 1
651 #define NEED_UPDATEKEYS 1
652 #include "keydb.c"
653
654 struct dbfuncs keydb_pg_funcs = {
655         .initdb                 = pg_initdb,
656         .cleanupdb              = pg_cleanupdb,
657         .starttrans             = pg_starttrans,
658         .endtrans               = pg_endtrans,
659         .fetch_key              = pg_fetch_key,
660         .fetch_key_text         = pg_fetch_key_text,
661         .store_key              = pg_store_key,
662         .update_keys            = generic_update_keys,
663         .delete_key             = pg_delete_key,
664         .getkeysigs             = pg_getkeysigs,
665         .cached_getkeysigs      = generic_cached_getkeysigs,
666         .keyid2uid              = pg_keyid2uid,
667         .getfullkeyid           = generic_getfullkeyid,
668         .iterate_keys           = pg_iterate_keys,
669 };