cscvs to tla changeset 18
[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 "keyindex.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, 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                                 fprintf(stderr, "Can't open large object.\n");
172                         } else {
173                                 read_openpgp_stream(keydb_fetchchar, &fd,
174                                                 &packets);
175                                 parse_keys(packets, publickey);
176                                 lo_close(dbconn, fd);
177                         }
178                 }
179         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
180                 fprintf(stderr, "Problem retrieving key from DB.\n");
181         }
182
183         PQclear(result);
184
185         if (!intrans) {
186                 result = PQexec(dbconn, "COMMIT");
187                 PQclear(result);
188         }
189         return (numkeys);
190 }
191
192 /**
193  *      fetch_key_text - Trys to find the keys that contain the supplied text.
194  *      @search: The text to search for.
195  *      @publickey: A pointer to a structure to return the key in.
196  *
197  *      This function searches for the supplied text and returns the keys that
198  *      contain it.
199  */
200 int fetch_key_text(const char *search, struct openpgp_publickey **publickey)
201 {
202         struct openpgp_packet_list *packets = NULL;
203         PGresult *result = NULL;
204         char *oids = NULL;
205         char statement[1024];
206         int fd = -1;
207         int i = 0;
208         int numkeys = 0;
209         Oid key_oid;
210         char *newsearch = NULL;
211
212         result = PQexec(dbconn, "BEGIN");
213         PQclear(result);
214
215         newsearch = malloc(strlen(search) * 2 + 1);
216         memset(newsearch, 0, strlen(search) * 2 + 1);
217         PQescapeString(newsearch, search, strlen(search));
218         snprintf(statement, 1023,
219                         "SELECT DISTINCT onak_keys.keydata FROM onak_keys, "
220                         "onak_uids WHERE onak_keys.keyid = onak_uids.keyid "
221                         "AND onak_uids.uid LIKE '%%%s%%'",
222                         newsearch);
223         result = PQexec(dbconn, statement);
224         free(newsearch);
225         newsearch = NULL;
226
227         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
228                 numkeys = PQntuples(result);
229                 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
230                         oids = PQgetvalue(result, i, 0);
231                         key_oid = (Oid) atoi(oids);
232
233                         fd = lo_open(dbconn, key_oid, INV_READ);
234                         if (fd < 0) {
235                                 fprintf(stderr, "Can't open large object.\n");
236                         } else {
237                                 read_openpgp_stream(keydb_fetchchar, &fd,
238                                                 &packets);
239                                 parse_keys(packets, publickey);
240                                 lo_close(dbconn, fd);
241                         }
242                 }
243         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
244                 fprintf(stderr, "Problem retrieving key from DB.\n");
245         }
246
247         PQclear(result);
248
249         result = PQexec(dbconn, "COMMIT");
250         PQclear(result);
251         return (numkeys);
252 }
253
254 /**
255  *      store_key - Takes a key and stores it.
256  *      @publickey: A pointer to the public key to store.
257  *      @intrans: If we're already in a transaction.
258  *      @update: If true the key exists and should be updated.
259  *
260  *      Again we just use the hex representation of the keyid as the filename
261  *      to store the key to. We flatten the public key to a list of OpenPGP
262  *      packets and then use write_openpgp_stream() to write the stream out to
263  *      the file. If update is true then we delete the old key first, otherwise
264  *      we trust that it doesn't exist.
265  */
266 int store_key(struct openpgp_publickey *publickey, bool intrans, bool update)
267 {
268         struct openpgp_packet_list *packets = NULL;
269         struct openpgp_packet_list *list_end = NULL;
270         struct openpgp_publickey *next = NULL;
271         struct openpgp_signedpacket_list *curuid = NULL;
272         PGresult *result = NULL;
273         char statement[1024];
274         Oid key_oid;
275         int fd;
276         char **uids = NULL;
277         char *primary = NULL;
278         char *safeuid = NULL;
279         int i;
280
281         if (!intrans) {
282                 result = PQexec(dbconn, "BEGIN");
283                 PQclear(result);
284         }
285
286         /*
287          * Delete the key if we already have it.
288          *
289          * TODO: Can we optimize this perhaps? Possibly when other data is
290          * involved as well? I suspect this is easiest and doesn't make a lot
291          * of difference though - the largest chunk of data is the keydata and
292          * it definitely needs updated.
293          */
294         if (update) {
295                 delete_key(get_keyid(publickey), true);
296         }
297
298         next = publickey->next;
299         publickey->next = NULL;
300         flatten_publickey(publickey, &packets, &list_end);
301         publickey->next = next;
302                 
303         key_oid = lo_creat(dbconn, INV_READ | INV_WRITE);
304         if (key_oid == 0) {
305                 fprintf(stderr, "Can't create key OID\n");
306         } else {
307                 fd = lo_open(dbconn, key_oid, INV_WRITE);
308                 write_openpgp_stream(keydb_putchar, &fd, packets);
309                 lo_close(dbconn, fd);
310         }
311
312         snprintf(statement, 1023, 
313                         "INSERT INTO onak_keys (keyid, keydata) VALUES "
314                         "('%llX', '%d')", 
315                         get_keyid(publickey),
316                         key_oid);
317         result = PQexec(dbconn, statement);
318
319         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
320                 fprintf(stderr, "Problem storing key in DB.\n");
321                 fprintf(stderr, "%s\n", PQresultErrorMessage(result));
322         }
323         PQclear(result);
324
325         uids = keyuids(publickey, &primary);
326         if (uids != NULL) {
327                 for (i = 0; uids[i] != NULL; i++) {
328                         safeuid = malloc(strlen(uids[i]) * 2 + 1);
329                         if (safeuid != NULL) {
330                                 memset(safeuid, 0, strlen(uids[i]) * 2 + 1);
331                                 PQescapeString(safeuid, uids[i],
332                                                 strlen(uids[i]));
333
334                                 snprintf(statement, 1023,
335                                         "INSERT INTO onak_uids "
336                                         "(keyid, uid, pri) "
337                                         "VALUES ('%llX', '%s', '%c')",
338                                         get_keyid(publickey),
339                                         safeuid,
340                                         (uids[i] == primary) ? 't' : 'f');
341                                 result = PQexec(dbconn, statement);
342
343                                 free(safeuid);
344                                 safeuid = NULL;
345                         }
346                         if (uids[i] != NULL) {
347                                 free(uids[i]);
348                                 uids[i] = NULL;
349                         }
350
351                         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
352                                 fprintf(stderr, "Problem storing key in DB.\n");
353                                 fprintf(stderr, "%s\n",
354                                                 PQresultErrorMessage(result));
355                         }
356                         /*
357                          * TODO: Check result.
358                          */
359                         PQclear(result);
360                 }
361                 free(uids);
362                 uids = NULL;
363         }
364
365         for (curuid = publickey->uids; curuid != NULL; curuid = curuid->next) {
366                 for (packets = curuid->sigs; packets != NULL; 
367                                 packets = packets->next) {
368                         snprintf(statement, 1023,
369                                 "INSERT INTO onak_sigs (signer, signee) "
370                                 "VALUES ('%llX', '%llX')",
371                                 sig_keyid(packets->packet),
372                                 get_keyid(publickey));
373                         result = PQexec(dbconn, statement);
374                         PQclear(result);
375                 }
376         }
377
378         if (!intrans) {
379                 result = PQexec(dbconn, "COMMIT");
380                 PQclear(result);
381         }
382         
383         return 0;
384 }
385
386 /**
387  *      delete_key - Given a keyid delete the key from storage.
388  *      @keyid: The keyid to delete.
389  *      @intrans: If we're already in a transaction.
390  *
391  *      This function deletes a public key from whatever storage mechanism we
392  *      are using. Returns 0 if the key existed.
393  */
394 int delete_key(uint64_t keyid, bool intrans)
395 {
396         PGresult *result = NULL;
397         char *oids = NULL;
398         char statement[1024];
399         int found = 1;
400         int i;
401         Oid key_oid;
402
403         if (!intrans) {
404                 result = PQexec(dbconn, "BEGIN");
405                 PQclear(result);
406         }
407         
408         snprintf(statement, 1023,
409                         "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
410                         keyid);
411         result = PQexec(dbconn, statement);
412
413         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
414                 found = 0;
415                 i = PQntuples(result);
416                 while (i > 0) {
417                         oids = PQgetvalue(result, i-1, 0);
418                         key_oid = (Oid) atoi(oids);
419                         lo_unlink(dbconn, key_oid);
420                         i--;
421                 }
422                 PQclear(result);
423
424                 snprintf(statement, 1023,
425                         "DELETE FROM onak_keys WHERE keyid = '%llX'",
426                         keyid);
427                 result = PQexec(dbconn, statement);
428                 PQclear(result);
429
430                 snprintf(statement, 1023,
431                         "DELETE FROM onak_sigs WHERE signee = '%llX'",
432                         keyid);
433                 result = PQexec(dbconn, statement);
434                 PQclear(result);
435
436                 snprintf(statement, 1023,
437                         "DELETE FROM onak_uids WHERE keyid = '%llX'",
438                         keyid);
439                 result = PQexec(dbconn, statement);
440         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
441                 fprintf(stderr, "Problem retrieving key (%llX) from DB.\n",
442                                 keyid);
443         }
444
445         PQclear(result);
446
447         if (!intrans) {
448                 result = PQexec(dbconn, "COMMIT");
449                 PQclear(result);
450         }
451         return (found);
452 }
453
454 /**
455  *      keyid2uid - Takes a keyid and returns the primary UID for it.
456  *      @keyid: The keyid to lookup.
457  */
458 char *keyid2uid(uint64_t keyid)
459 {
460         PGresult *result = NULL;
461         char statement[1024];
462         char *uid = NULL;
463
464         snprintf(statement, 1023,
465                 "SELECT uid FROM onak_uids WHERE keyid = '%llX' AND pri = 't'",
466                 keyid);
467         result = PQexec(dbconn, statement);
468
469         /*
470          * Technically we only expect one response to the query; a key only has
471          * one primary ID. Better to return something than nothing though.
472          *
473          * TODO: Log if we get more than one response? Needs logging framework
474          * first though.
475          */
476         if (PQresultStatus(result) == PGRES_TUPLES_OK &&
477                         PQntuples(result) >= 1) {
478                 uid = strdup(PQgetvalue(result, 0, 0));
479         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
480                 fprintf(stderr, "Problem retrieving key (%llX) from DB.\n",
481                                 keyid);
482         }
483
484         PQclear(result);
485
486         return uid;
487 }
488
489 /**
490  *      getkeysigs - Gets a linked list of the signatures on a key.
491  *      @keyid: The keyid to get the sigs for.
492  *
493  *      This function gets the list of signatures on a key. Used for key 
494  *      indexing and doing stats bits.
495  */
496 struct ll *getkeysigs(uint64_t keyid)
497 {
498         struct ll *sigs = NULL;
499         PGresult *result = NULL;
500         uint64_t signer;
501         char statement[1024];
502         int i = 0;
503         int numsigs = 0;
504         bool intrans = false;
505
506         if (!intrans) {
507                 result = PQexec(dbconn, "BEGIN");
508                 PQclear(result);
509         }
510
511         snprintf(statement, 1023,
512                 "SELECT signer FROM onak_sigs WHERE signee = '%llX'",
513                 keyid);
514         result = PQexec(dbconn, statement);
515
516         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
517                 numsigs = PQntuples(result);
518                 for (i = 0; i < numsigs;  i++) {
519                         signer = strtol(PQgetvalue(result, i, 0), NULL, 16);
520                         sigs = lladd(sigs, createandaddtohash(signer));
521                 }
522         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
523                 fprintf(stderr, "Problem retrieving key from DB.\n");
524         }
525
526         PQclear(result);
527
528         if (!intrans) {
529                 result = PQexec(dbconn, "COMMIT");
530                 PQclear(result);
531         }
532         return sigs;
533 }
534
535 /*
536  * Include the basic keydb routines.
537  */
538 #define NEED_GETFULLKEYID 1
539 #include "keydb.c"