cscvs to tla changeset 3
[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 *dodgychar = NULL;
210
211         result = PQexec(dbconn, "BEGIN");
212         PQclear(result);
213
214         /*
215          * TODO: We really want to use PQescapeString, but this isn't supported
216          * by the version of Postgresql in Debian Stable. Roll on Woody and for
217          * now kludge it.
218          */
219         dodgychar = strchr(search, '\'');
220         while (dodgychar != NULL) {
221                 *dodgychar = ' ';
222                 dodgychar = strchr(search, '\'');
223         }
224         dodgychar = strchr(search, '\\');
225         while (dodgychar != NULL) {
226                 *dodgychar = ' ';
227                 dodgychar = strchr(search, '\\');
228         }
229
230         
231         snprintf(statement, 1023,
232                         "SELECT DISTINCT onak_keys.keydata FROM onak_keys, "
233                         "onak_uids WHERE onak_keys.keyid = onak_uids.keyid "
234                         "AND onak_uids.uid LIKE '%%%s%%'",
235                         search);
236         result = PQexec(dbconn, statement);
237
238         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
239                 numkeys = PQntuples(result);
240                 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
241                         oids = PQgetvalue(result, i, 0);
242                         key_oid = (Oid) atoi(oids);
243
244                         fd = lo_open(dbconn, key_oid, INV_READ);
245                         if (fd < 0) {
246                                 fprintf(stderr, "Can't open large object.\n");
247                         } else {
248                                 read_openpgp_stream(keydb_fetchchar, &fd,
249                                                 &packets);
250                                 parse_keys(packets, publickey);
251                                 lo_close(dbconn, fd);
252                         }
253                 }
254         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
255                 fprintf(stderr, "Problem retrieving key from DB.\n");
256         }
257
258         PQclear(result);
259
260         result = PQexec(dbconn, "COMMIT");
261         PQclear(result);
262         return (numkeys);
263 }
264
265 /**
266  *      store_key - Takes a key and stores it.
267  *      @publickey: A pointer to the public key to store.
268  *      @intrans: If we're already in a transaction.
269  *      @update: If true the key exists and should be updated.
270  *
271  *      Again we just use the hex representation of the keyid as the filename
272  *      to store the key to. We flatten the public key to a list of OpenPGP
273  *      packets and then use write_openpgp_stream() to write the stream out to
274  *      the file. If update is true then we delete the old key first, otherwise we
275  *      trust that it doesn't exist.
276  */
277 int store_key(struct openpgp_publickey *publickey, bool intrans, bool update)
278 {
279         struct openpgp_packet_list *packets = NULL;
280         struct openpgp_packet_list *list_end = NULL;
281         struct openpgp_publickey *next = 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 *dodgychar = 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                 fprintf(stderr, "Can't create key OID\n");
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
322         snprintf(statement, 1023, 
323                         "INSERT INTO onak_keys (keyid, keydata) VALUES "
324                         "('%llX', '%d')", 
325                         get_keyid(publickey),
326                         key_oid);
327         result = PQexec(dbconn, statement);
328
329         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
330                 fprintf(stderr, "Problem storing key in DB.\n");
331                 fprintf(stderr, "%s\n", PQresultErrorMessage(result));
332         }
333         PQclear(result);
334
335         uids = keyuids(publickey, &primary);
336         if (uids != NULL) {
337                 for (i = 0; uids[i] != NULL; i++) {
338                         /*
339                          * TODO: We really want to use PQescapeString, but this
340                          * isn't supported by the version of Postgresql in
341                          * Debian Stable. Roll on Woody and for now kludge it.
342                          */
343                         dodgychar = strchr(uids[i], '\'');
344                         while (dodgychar != NULL) {
345                                 *dodgychar = ' ';
346                                 dodgychar = strchr(uids[i], '\'');
347                         }
348                         dodgychar = strchr(uids[i], '\\');
349                                 while (dodgychar != NULL) {
350                                 *dodgychar = ' ';
351                                 dodgychar = strchr(uids[i], '\\');
352                         }
353
354                         snprintf(statement, 1023,
355                                 "INSERT INTO onak_uids (keyid, uid, pri) "
356                                 "VALUES ('%llX', '%s', '%c')",
357                                 get_keyid(publickey),
358                                 uids[i],
359                                 (uids[i] == primary) ? 't' : 'f');
360                         result = PQexec(dbconn, statement);
361
362                         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
363                                 fprintf(stderr, "Problem storing key in DB.\n");
364                                 fprintf(stderr, "%s\n",
365                                                 PQresultErrorMessage(result));
366                         }
367                         /*
368                          * TODO: Check result.
369                          */
370                         PQclear(result);
371                 }
372         }
373
374         if (!intrans) {
375                 result = PQexec(dbconn, "COMMIT");
376                 PQclear(result);
377         }
378         
379         return 0;
380 }
381
382 /**
383  *      delete_key - Given a keyid delete the key from storage.
384  *      @keyid: The keyid to delete.
385  *      @intrans: If we're already in a transaction.
386  *
387  *      This function deletes a public key from whatever storage mechanism we
388  *      are using. Returns 0 if the key existed.
389  */
390 int delete_key(uint64_t keyid, bool intrans)
391 {
392         PGresult *result = NULL;
393         char *oids = NULL;
394         char statement[1024];
395         int found = 1;
396         int i;
397         Oid key_oid;
398
399         if (!intrans) {
400                 result = PQexec(dbconn, "BEGIN");
401                 PQclear(result);
402         }
403         
404         snprintf(statement, 1023,
405                         "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
406                         keyid);
407         result = PQexec(dbconn, statement);
408
409         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
410                 found = 0;
411                 i = PQntuples(result);
412                 while (i > 0) {
413                         oids = PQgetvalue(result, i-1, 0);
414                         key_oid = (Oid) atoi(oids);
415                         lo_unlink(dbconn, key_oid);
416                         i--;
417                 }
418                 PQclear(result);
419
420                 snprintf(statement, 1023,
421                         "DELETE FROM onak_keys WHERE keyid = '%llX'",
422                         keyid);
423                 result = PQexec(dbconn, statement);
424                 PQclear(result);
425
426                 snprintf(statement, 1023,
427                         "DELETE FROM onak_uids WHERE keyid = '%llX'",
428                         keyid);
429                 result = PQexec(dbconn, statement);
430         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
431                 fprintf(stderr, "Problem retrieving key (%llX) from DB.\n",
432                                 keyid);
433         }
434
435         PQclear(result);
436
437         if (!intrans) {
438                 result = PQexec(dbconn, "COMMIT");
439                 PQclear(result);
440         }
441         return (found);
442 }
443
444 /**
445  *      keyid2uid - Takes a keyid and returns the primary UID for it.
446  *      @keyid: The keyid to lookup.
447  */
448 char *keyid2uid(uint64_t keyid)
449 {
450         PGresult *result = NULL;
451         char statement[1024];
452         char *uid = NULL;
453
454         snprintf(statement, 1023,
455                 "SELECT uid FROM onak_uids WHERE keyid = '%llX' AND pri = 't'",
456                 keyid);
457         result = PQexec(dbconn, statement);
458
459         /*
460          * Technically we only expect one response to the query; a key only has
461          * one primary ID. Better to return something than nothing though.
462          *
463          * TODO: Log if we get more than one response? Needs logging framework
464          * first though.
465          */
466         if (PQresultStatus(result) == PGRES_TUPLES_OK &&
467                         PQntuples(result) >= 1) {
468                 uid = strdup(PQgetvalue(result, 0, 0));
469         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
470                 fprintf(stderr, "Problem retrieving key (%llX) from DB.\n",
471                                 keyid);
472         }
473
474         PQclear(result);
475
476         return uid;
477 }
478
479 /*
480  * Include the basic keydb routines.
481  */
482 #define NEED_GETKEYSIGS 1
483 #define NEED_GETFULLKEYID 1
484 #include "keydb.c"