cscvs to tla changeset 47
[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
9 #include <postgresql/libpq-fe.h>
10 #include <postgresql/libpq/libpq-fs.h>
11
12 //#include <libpq-fe.h>
13 //#include <libpq/libpq-fs.h>
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 "mem.h"
29 #include "onak-conf.h"
30 #include "parsekey.h"
31
32 /**
33  *      dbconn - our connection to the database.
34  */
35 static PGconn *dbconn = NULL;
36
37 /**
38  *      keydb_fetchchar - Fetches a char from a file.
39  */
40 static int keydb_fetchchar(void *fd, size_t count, unsigned char *c)
41 {
42         return (!lo_read(dbconn, *(int *) fd, c, count));
43 }
44
45 /**
46  *      keydb_putchar - Puts a char to a file.
47  */
48 static int keydb_putchar(void *fd, size_t count, unsigned char *c)
49 {
50         return !(lo_write(dbconn, *(int *) fd, c, count));
51 }
52
53 /**
54  *      initdb - Initialize the key database.
55  *
56  *      This function should be called before any of the other functions in
57  *      this file are called in order to allow the DB to be initialized ready
58  *      for access.
59  */
60 void initdb(void)
61 {
62         dbconn = PQsetdbLogin(config.pg_dbhost, // host
63                         NULL, // port
64                         NULL, // options
65                         NULL, // tty
66                         config.pg_dbname, // database
67                         config.pg_dbuser,  //login
68                         config.pg_dbpass); // password
69
70         if (PQstatus(dbconn) == CONNECTION_BAD) {
71                 fprintf(stderr, "Connection to database failed.\n");
72                 fprintf(stderr, "%s\n", PQerrorMessage(dbconn));
73                 PQfinish(dbconn);
74                 dbconn = NULL;
75                 exit(1);
76         }
77 }
78
79 /**
80  *      cleanupdb - De-initialize the key database.
81  *
82  *      This function should be called upon program exit to allow the DB to
83  *      cleanup after itself.
84  */
85 void cleanupdb(void)
86 {
87         PQfinish(dbconn);
88         dbconn = NULL;
89 }
90
91 /**
92  *      starttrans - Start a transaction.
93  *
94  *      Start a transaction. Intended to be used if we're about to perform many
95  *      operations on the database to help speed it all up, or if we want
96  *      something to only succeed if all relevant operations are successful.
97  */
98 bool starttrans(void)
99 {
100         PGresult *result = NULL;
101         
102         result = PQexec(dbconn, "BEGIN");
103         PQclear(result);
104
105         return true;
106 }
107
108 /**
109  *      endtrans - End a transaction.
110  *
111  *      Ends a transaction.
112  */
113 void endtrans(void)
114 {
115         PGresult *result = NULL;
116
117         result = PQexec(dbconn, "COMMIT");
118         PQclear(result);
119
120         return;
121 }
122
123 /**
124  *      fetch_key - Given a keyid fetch the key from storage.
125  *      @keyid: The keyid to fetch.
126  *      @publickey: A pointer to a structure to return the key in.
127  *      @intrans: If we're already in a transaction.
128  *
129  *      We use the hex representation of the keyid as the filename to fetch the
130  *      key from. The key is stored in the file as a binary OpenPGP stream of
131  *      packets, so we can just use read_openpgp_stream() to read the packets
132  *      in and then parse_keys() to parse the packets into a publickey
133  *      structure.
134  */
135 int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey,
136                 bool intrans)
137 {
138         struct openpgp_packet_list *packets = NULL;
139         PGresult *result = NULL;
140         char *oids = NULL;
141         char statement[1024];
142         int fd = -1;
143         int i = 0;
144         int numkeys = 0;
145         Oid key_oid;
146
147         if (!intrans) {
148                 result = PQexec(dbconn, "BEGIN");
149                 PQclear(result);
150         }
151         
152         if (keyid > 0xFFFFFFFF) {
153                 snprintf(statement, 1023,
154                         "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
155                         keyid);
156         } else {
157                 snprintf(statement, 1023,
158                         "SELECT keydata FROM onak_keys WHERE keyid "
159                         "LIKE '%%%llX'",
160                         keyid);
161         }
162         result = PQexec(dbconn, statement);
163
164         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
165                 numkeys = PQntuples(result);
166                 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
167                         oids = PQgetvalue(result, i, 0);
168                         key_oid = (Oid) atoi(oids);
169
170                         fd = lo_open(dbconn, key_oid, INV_READ);
171                         if (fd < 0) {
172                                 fprintf(stderr, "Can't open large object.\n");
173                         } else {
174                                 read_openpgp_stream(keydb_fetchchar, &fd,
175                                                 &packets);
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                 fprintf(stderr, "Problem retrieving key from DB.\n");
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 int fetch_key_text(const char *search, struct openpgp_publickey **publickey)
204 {
205         struct openpgp_packet_list *packets = NULL;
206         PGresult *result = NULL;
207         char *oids = NULL;
208         char statement[1024];
209         int fd = -1;
210         int i = 0;
211         int numkeys = 0;
212         Oid key_oid;
213         char *newsearch = NULL;
214
215         result = PQexec(dbconn, "BEGIN");
216         PQclear(result);
217
218         newsearch = malloc(strlen(search) * 2 + 1);
219         memset(newsearch, 0, strlen(search) * 2 + 1);
220         PQescapeString(newsearch, search, strlen(search));
221         snprintf(statement, 1023,
222                         "SELECT DISTINCT onak_keys.keydata FROM onak_keys, "
223                         "onak_uids WHERE onak_keys.keyid = onak_uids.keyid "
224                         "AND onak_uids.uid LIKE '%%%s%%'",
225                         newsearch);
226         result = PQexec(dbconn, statement);
227         free(newsearch);
228         newsearch = NULL;
229
230         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
231                 numkeys = PQntuples(result);
232                 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
233                         oids = PQgetvalue(result, i, 0);
234                         key_oid = (Oid) atoi(oids);
235
236                         fd = lo_open(dbconn, key_oid, INV_READ);
237                         if (fd < 0) {
238                                 fprintf(stderr, "Can't open large object.\n");
239                         } else {
240                                 read_openpgp_stream(keydb_fetchchar, &fd,
241                                                 &packets);
242                                 parse_keys(packets, publickey);
243                                 lo_close(dbconn, fd);
244                                 free_packet_list(packets);
245                                 packets = NULL;
246                         }
247                 }
248         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
249                 fprintf(stderr, "Problem retrieving key from DB.\n");
250         }
251
252         PQclear(result);
253
254         result = PQexec(dbconn, "COMMIT");
255         PQclear(result);
256         return (numkeys);
257 }
258
259 /**
260  *      store_key - Takes a key and stores it.
261  *      @publickey: A pointer to the public key to store.
262  *      @intrans: If we're already in a transaction.
263  *      @update: If true the key exists and should be updated.
264  *
265  *      Again we just use the hex representation of the keyid as the filename
266  *      to store the key to. We flatten the public key to a list of OpenPGP
267  *      packets and then use write_openpgp_stream() to write the stream out to
268  *      the file. If update is true then we delete the old key first, otherwise
269  *      we trust that it doesn't exist.
270  */
271 int store_key(struct openpgp_publickey *publickey, bool intrans, bool update)
272 {
273         struct openpgp_packet_list *packets = NULL;
274         struct openpgp_packet_list *list_end = NULL;
275         struct openpgp_publickey *next = NULL;
276         struct openpgp_signedpacket_list *curuid = NULL;
277         PGresult *result = NULL;
278         char statement[1024];
279         Oid key_oid;
280         int fd;
281         char **uids = NULL;
282         char *primary = NULL;
283         char *safeuid = NULL;
284         int i;
285
286         if (!intrans) {
287                 result = PQexec(dbconn, "BEGIN");
288                 PQclear(result);
289         }
290
291         /*
292          * Delete the key if we already have it.
293          *
294          * TODO: Can we optimize this perhaps? Possibly when other data is
295          * involved as well? I suspect this is easiest and doesn't make a lot
296          * of difference though - the largest chunk of data is the keydata and
297          * it definitely needs updated.
298          */
299         if (update) {
300                 delete_key(get_keyid(publickey), true);
301         }
302
303         next = publickey->next;
304         publickey->next = NULL;
305         flatten_publickey(publickey, &packets, &list_end);
306         publickey->next = next;
307                 
308         key_oid = lo_creat(dbconn, INV_READ | INV_WRITE);
309         if (key_oid == 0) {
310                 fprintf(stderr, "Can't create key OID\n");
311         } else {
312                 fd = lo_open(dbconn, key_oid, INV_WRITE);
313                 write_openpgp_stream(keydb_putchar, &fd, packets);
314                 lo_close(dbconn, fd);
315         }
316         free_packet_list(packets);
317         packets = NULL;
318
319         snprintf(statement, 1023, 
320                         "INSERT INTO onak_keys (keyid, keydata) VALUES "
321                         "('%llX', '%d')", 
322                         get_keyid(publickey),
323                         key_oid);
324         result = PQexec(dbconn, statement);
325
326         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
327                 fprintf(stderr, "Problem storing key in DB.\n");
328                 fprintf(stderr, "%s\n", PQresultErrorMessage(result));
329         }
330         PQclear(result);
331
332         uids = keyuids(publickey, &primary);
333         if (uids != NULL) {
334                 for (i = 0; uids[i] != NULL; i++) {
335                         safeuid = malloc(strlen(uids[i]) * 2 + 1);
336                         if (safeuid != NULL) {
337                                 memset(safeuid, 0, strlen(uids[i]) * 2 + 1);
338                                 PQescapeString(safeuid, uids[i],
339                                                 strlen(uids[i]));
340
341                                 snprintf(statement, 1023,
342                                         "INSERT INTO onak_uids "
343                                         "(keyid, uid, pri) "
344                                         "VALUES ('%llX', '%s', '%c')",
345                                         get_keyid(publickey),
346                                         safeuid,
347                                         (uids[i] == primary) ? 't' : 'f');
348                                 result = PQexec(dbconn, statement);
349
350                                 free(safeuid);
351                                 safeuid = NULL;
352                         }
353                         if (uids[i] != NULL) {
354                                 free(uids[i]);
355                                 uids[i] = NULL;
356                         }
357
358                         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
359                                 fprintf(stderr, "Problem storing key in DB.\n");
360                                 fprintf(stderr, "%s\n",
361                                                 PQresultErrorMessage(result));
362                         }
363                         /*
364                          * TODO: Check result.
365                          */
366                         PQclear(result);
367                 }
368                 free(uids);
369                 uids = NULL;
370         }
371
372         for (curuid = publickey->uids; curuid != NULL; curuid = curuid->next) {
373                 for (packets = curuid->sigs; packets != NULL; 
374                                 packets = packets->next) {
375                         snprintf(statement, 1023,
376                                 "INSERT INTO onak_sigs (signer, signee) "
377                                 "VALUES ('%llX', '%llX')",
378                                 sig_keyid(packets->packet),
379                                 get_keyid(publickey));
380                         result = PQexec(dbconn, statement);
381                         PQclear(result);
382                 }
383         }
384
385         if (!intrans) {
386                 result = PQexec(dbconn, "COMMIT");
387                 PQclear(result);
388         }
389         
390         return 0;
391 }
392
393 /**
394  *      delete_key - Given a keyid delete the key from storage.
395  *      @keyid: The keyid to delete.
396  *      @intrans: If we're already in a transaction.
397  *
398  *      This function deletes a public key from whatever storage mechanism we
399  *      are using. Returns 0 if the key existed.
400  */
401 int delete_key(uint64_t keyid, bool intrans)
402 {
403         PGresult *result = NULL;
404         char *oids = NULL;
405         char statement[1024];
406         int found = 1;
407         int i;
408         Oid key_oid;
409
410         if (!intrans) {
411                 result = PQexec(dbconn, "BEGIN");
412                 PQclear(result);
413         }
414         
415         snprintf(statement, 1023,
416                         "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
417                         keyid);
418         result = PQexec(dbconn, statement);
419
420         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
421                 found = 0;
422                 i = PQntuples(result);
423                 while (i > 0) {
424                         oids = PQgetvalue(result, i-1, 0);
425                         key_oid = (Oid) atoi(oids);
426                         lo_unlink(dbconn, key_oid);
427                         i--;
428                 }
429                 PQclear(result);
430
431                 snprintf(statement, 1023,
432                         "DELETE FROM onak_keys WHERE keyid = '%llX'",
433                         keyid);
434                 result = PQexec(dbconn, statement);
435                 PQclear(result);
436
437                 snprintf(statement, 1023,
438                         "DELETE FROM onak_sigs WHERE signee = '%llX'",
439                         keyid);
440                 result = PQexec(dbconn, statement);
441                 PQclear(result);
442
443                 snprintf(statement, 1023,
444                         "DELETE FROM onak_uids WHERE keyid = '%llX'",
445                         keyid);
446                 result = PQexec(dbconn, statement);
447         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
448                 fprintf(stderr, "Problem retrieving key (%llX) from DB.\n",
449                                 keyid);
450         }
451
452         PQclear(result);
453
454         if (!intrans) {
455                 result = PQexec(dbconn, "COMMIT");
456                 PQclear(result);
457         }
458         return (found);
459 }
460
461 /**
462  *      keyid2uid - Takes a keyid and returns the primary UID for it.
463  *      @keyid: The keyid to lookup.
464  */
465 char *keyid2uid(uint64_t keyid)
466 {
467         PGresult *result = NULL;
468         char statement[1024];
469         char *uid = NULL;
470
471         snprintf(statement, 1023,
472                 "SELECT uid FROM onak_uids WHERE keyid = '%llX' AND pri = 't'",
473                 keyid);
474         result = PQexec(dbconn, statement);
475
476         /*
477          * Technically we only expect one response to the query; a key only has
478          * one primary ID. Better to return something than nothing though.
479          *
480          * TODO: Log if we get more than one response? Needs logging framework
481          * first though.
482          */
483         if (PQresultStatus(result) == PGRES_TUPLES_OK &&
484                         PQntuples(result) >= 1) {
485                 uid = strdup(PQgetvalue(result, 0, 0));
486         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
487                 fprintf(stderr, "Problem retrieving key (%llX) from DB.\n",
488                                 keyid);
489         }
490
491         PQclear(result);
492
493         return uid;
494 }
495
496 /**
497  *      getkeysigs - Gets a linked list of the signatures on a key.
498  *      @keyid: The keyid to get the sigs for.
499  *
500  *      This function gets the list of signatures on a key. Used for key 
501  *      indexing and doing stats bits.
502  */
503 struct ll *getkeysigs(uint64_t keyid)
504 {
505         struct ll *sigs = NULL;
506         PGresult *result = NULL;
507         uint64_t signer;
508         char statement[1024];
509         int i, j;
510         int numsigs = 0;
511         bool intrans = false;
512         char *str;
513
514         if (!intrans) {
515                 result = PQexec(dbconn, "BEGIN");
516                 PQclear(result);
517         }
518
519         snprintf(statement, 1023,
520                 "SELECT DISTINCT signer FROM onak_sigs WHERE signee = '%llX'",
521                 keyid);
522         result = PQexec(dbconn, statement);
523
524         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
525                 numsigs = PQntuples(result);
526                 for (i = 0; i < numsigs;  i++) {
527                         j = 0;
528                         signer = 0;
529                         str = PQgetvalue(result, i, 0);
530                         while (str[j] != 0) {
531                                 signer <<= 4;
532                                 if (str[j] >= '0' && str[j] <= '9') {
533                                         signer += str[j] - '0';
534                                 } else {
535                                         signer += str[j] - 'A' + 10;
536                                 }
537                                 j++;
538                         }
539                         sigs = lladd(sigs, createandaddtohash(signer));
540                 }
541         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
542                 fprintf(stderr, "Problem retrieving key from DB.\n");
543         }
544
545         PQclear(result);
546
547         if (!intrans) {
548                 result = PQexec(dbconn, "COMMIT");
549                 PQclear(result);
550         }
551         return sigs;
552 }
553
554 /*
555  * Include the basic keydb routines.
556  */
557 #define NEED_GETFULLKEYID 1
558 #include "keydb.c"