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