cscvs to tla changeset 2
[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, unsigned char c)
48 {
49         return !(lo_write(dbconn, *(int *) fd, &c, sizeof(c)));
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  *      fetch_key - Given a keyid fetch the key from storage.
92  *      @keyid: The keyid to fetch.
93  *      @publickey: A pointer to a structure to return the key in.
94  *
95  *      We use the hex representation of the keyid as the filename to fetch the
96  *      key from. The key is stored in the file as a binary OpenPGP stream of
97  *      packets, so we can just use read_openpgp_stream() to read the packets
98  *      in and then parse_keys() to parse the packets into a publickey
99  *      structure.
100  */
101 int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey)
102 {
103         struct openpgp_packet_list *packets = NULL;
104         PGresult *result = NULL;
105         char *oids = NULL;
106         char statement[1024];
107         int fd = -1;
108         int i = 0;
109         int numkeys = 0;
110         Oid key_oid;
111
112         result = PQexec(dbconn, "BEGIN");
113         PQclear(result);
114         
115         if (keyid > 0xFFFFFFFF) {
116                 snprintf(statement, 1023,
117                         "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
118                         keyid);
119         } else {
120                 snprintf(statement, 1023,
121                         "SELECT keydata FROM onak_keys WHERE keyid "
122                         "LIKE '%%%llX'",
123                         keyid);
124         }
125         result = PQexec(dbconn, statement);
126
127         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
128                 numkeys = PQntuples(result);
129                 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
130                         oids = PQgetvalue(result, i, 0);
131                         key_oid = (Oid) atoi(oids);
132
133                         fd = lo_open(dbconn, key_oid, INV_READ);
134                         if (fd < 0) {
135                                 fprintf(stderr, "Can't open large object.\n");
136                         } else {
137                                 read_openpgp_stream(keydb_fetchchar, &fd,
138                                                 &packets);
139                                 parse_keys(packets, publickey);
140                                 lo_close(dbconn, fd);
141                         }
142                 }
143         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
144                 fprintf(stderr, "Problem retrieving key from DB.\n");
145         }
146
147         PQclear(result);
148
149         result = PQexec(dbconn, "COMMIT");
150         PQclear(result);
151         return (numkeys);
152 }
153
154 /**
155  *      fetch_key_text - Trys to find the keys that contain the supplied text.
156  *      @search: The text to search for.
157  *      @publickey: A pointer to a structure to return the key in.
158  *
159  *      This function searches for the supplied text and returns the keys that
160  *      contain it.
161  */
162 int fetch_key_text(const char *search, struct openpgp_publickey **publickey)
163 {
164         struct openpgp_packet_list *packets = NULL;
165         PGresult *result = NULL;
166         char *oids = NULL;
167         char statement[1024];
168         int fd = -1;
169         int i = 0;
170         int numkeys = 0;
171         Oid key_oid;
172         char *dodgychar = NULL;
173
174         result = PQexec(dbconn, "BEGIN");
175         PQclear(result);
176
177         /*
178          * TODO: We really want to use PQescapeString, but this isn't supported
179          * by the version of Postgresql in Debian Stable. Roll on Woody and for
180          * now kludge it.
181          */
182         dodgychar = strchr(search, '\'');
183         while (dodgychar != NULL) {
184                 *dodgychar = ' ';
185                 dodgychar = strchr(search, '\'');
186         }
187         dodgychar = strchr(search, '\\');
188         while (dodgychar != NULL) {
189                 *dodgychar = ' ';
190                 dodgychar = strchr(search, '\\');
191         }
192
193         
194         snprintf(statement, 1023,
195                         "SELECT DISTINCT onak_keys.keydata FROM onak_keys, "
196                         "onak_uids WHERE onak_keys.keyid = onak_uids.keyid "
197                         "AND onak_uids.uid LIKE '%%%s%%'",
198                         search);
199         result = PQexec(dbconn, statement);
200
201         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
202                 numkeys = PQntuples(result);
203                 for (i = 0; i < numkeys && numkeys <= config.maxkeys; i++) {
204                         oids = PQgetvalue(result, i, 0);
205                         key_oid = (Oid) atoi(oids);
206
207                         fd = lo_open(dbconn, key_oid, INV_READ);
208                         if (fd < 0) {
209                                 fprintf(stderr, "Can't open large object.\n");
210                         } else {
211                                 read_openpgp_stream(keydb_fetchchar, &fd,
212                                                 &packets);
213                                 parse_keys(packets, publickey);
214                                 lo_close(dbconn, fd);
215                         }
216                 }
217         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
218                 fprintf(stderr, "Problem retrieving key from DB.\n");
219         }
220
221         PQclear(result);
222
223         result = PQexec(dbconn, "COMMIT");
224         PQclear(result);
225         return (numkeys);
226 }
227
228 /**
229  *      store_key - Takes a key and stores it.
230  *      @publickey: A pointer to the public key to store.
231  *
232  *      Again we just use the hex representation of the keyid as the filename
233  *      to store the key to. We flatten the public key to a list of OpenPGP
234  *      packets and then use write_openpgp_stream() to write the stream out to
235  *      the file.
236  */
237 int store_key(struct openpgp_publickey *publickey)
238 {
239         struct openpgp_packet_list *packets = NULL;
240         struct openpgp_packet_list *list_end = NULL;
241         struct openpgp_publickey *next = NULL;
242         PGresult *result = NULL;
243         char statement[1024];
244         Oid key_oid;
245         int fd;
246         char **uids = NULL;
247         char *primary = NULL;
248         char *dodgychar = NULL;
249         int i;
250
251
252         /*
253          * Delete the key if we already have it.
254          *
255          * TODO: Can we optimize this perhaps? Possibly when other data is
256          * involved as well? I suspect this is easiest and doesn't make a lot
257          * of difference though - the largest chunk of data is the keydata and
258          * it definitely needs updated.
259          */
260         delete_key(get_keyid(publickey));
261
262         result = PQexec(dbconn, "BEGIN");
263         PQclear(result);
264
265         next = publickey->next;
266         publickey->next = NULL;
267         flatten_publickey(publickey, &packets, &list_end);
268         publickey->next = next;
269                 
270         key_oid = lo_creat(dbconn, INV_READ | INV_WRITE);
271         if (key_oid == 0) {
272                 fprintf(stderr, "Can't create key OID\n");
273         } else {
274                 fd = lo_open(dbconn, key_oid, INV_WRITE);
275                 write_openpgp_stream(keydb_putchar, &fd, packets);
276                 lo_close(dbconn, fd);
277         }
278
279         snprintf(statement, 1023, 
280                         "INSERT INTO onak_keys (keyid, keydata) VALUES "
281                         "('%llX', '%d')", 
282                         get_keyid(publickey),
283                         key_oid);
284         result = PQexec(dbconn, statement);
285
286         if (PQresultStatus(result) != PGRES_COMMAND_OK) {
287                 fprintf(stderr, "Problem storing key in DB.\n");
288                 fprintf(stderr, "%s\n", PQresultErrorMessage(result));
289         }
290         PQclear(result);
291
292         uids = keyuids(publickey, &primary);
293         if (uids != NULL) {
294                 for (i = 0; uids[i] != NULL; i++) {
295                         /*
296                          * TODO: We really want to use PQescapeString, but this
297                          * isn't supported by the version of Postgresql in
298                          * Debian Stable. Roll on Woody and for now kludge it.
299                          */
300                         dodgychar = strchr(uids[i], '\'');
301                         while (dodgychar != NULL) {
302                                 *dodgychar = ' ';
303                                 dodgychar = strchr(uids[i], '\'');
304                         }
305                         dodgychar = strchr(uids[i], '\\');
306                                 while (dodgychar != NULL) {
307                                 *dodgychar = ' ';
308                                 dodgychar = strchr(uids[i], '\\');
309                         }
310
311                         snprintf(statement, 1023,
312                                 "INSERT INTO onak_uids (keyid, uid, pri) "
313                                 "VALUES ('%llX', '%s', '%c')",
314                                 get_keyid(publickey),
315                                 uids[i],
316                                 (uids[i] == primary) ? 't' : 'f');
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",
322                                                 PQresultErrorMessage(result));
323                         }
324                         /*
325                          * TODO: Check result.
326                          */
327                         PQclear(result);
328                 }
329         }
330
331         result = PQexec(dbconn, "COMMIT");
332         PQclear(result);
333         
334         return 0;
335 }
336
337 /**
338  *      delete_key - Given a keyid delete the key from storage.
339  *      @keyid: The keyid to delete.
340  *
341  *      This function deletes a public key from whatever storage mechanism we
342  *      are using. Returns 0 if the key existed.
343  */
344 int delete_key(uint64_t keyid)
345 {
346         PGresult *result = NULL;
347         char *oids = NULL;
348         char statement[1024];
349         int found = 1;
350         int i;
351         Oid key_oid;
352
353         result = PQexec(dbconn, "BEGIN");
354         PQclear(result);
355         
356         snprintf(statement, 1023,
357                         "SELECT keydata FROM onak_keys WHERE keyid = '%llX'",
358                         keyid);
359         result = PQexec(dbconn, statement);
360
361         if (PQresultStatus(result) == PGRES_TUPLES_OK) {
362                 found = 0;
363                 i = PQntuples(result);
364                 while (i > 0) {
365                         oids = PQgetvalue(result, i-1, 0);
366                         key_oid = (Oid) atoi(oids);
367                         lo_unlink(dbconn, key_oid);
368                         i--;
369                 }
370                 PQclear(result);
371
372                 snprintf(statement, 1023,
373                         "DELETE FROM onak_keys WHERE keyid = '%llX'",
374                         keyid);
375                 result = PQexec(dbconn, statement);
376                 PQclear(result);
377
378                 snprintf(statement, 1023,
379                         "DELETE FROM onak_uids WHERE keyid = '%llX'",
380                         keyid);
381                 result = PQexec(dbconn, statement);
382         } else if (PQresultStatus(result) != PGRES_TUPLES_OK) {
383                 fprintf(stderr, "Problem retrieving key (%llX) from DB.\n",
384                                 keyid);
385         }
386
387         PQclear(result);
388
389         result = PQexec(dbconn, "COMMIT");
390         PQclear(result);
391         return (found);
392 }
393
394 /**
395  *      keyid2uid - Takes a keyid and returns the primary UID for it.
396  *      @keyid: The keyid to lookup.
397  */
398 char *keyid2uid(uint64_t keyid)
399 {
400         PGresult *result = NULL;
401         char statement[1024];
402         char *uid = NULL;
403
404         snprintf(statement, 1023,
405                 "SELECT uid FROM onak_uids WHERE keyid = '%llX' AND pri = 't'",
406                 keyid);
407         result = PQexec(dbconn, statement);
408
409         /*
410          * Technically we only expect one response to the query; a key only has
411          * one primary ID. Better to return something than nothing though.
412          *
413          * TODO: Log if we get more than one response? Needs logging framework
414          * first though.
415          */
416         if (PQresultStatus(result) == PGRES_TUPLES_OK &&
417                         PQntuples(result) >= 1) {
418                 uid = strdup(PQgetvalue(result, 0, 0));
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         return uid;
427 }
428
429 /*
430  * Include the basic keydb routines.
431  */
432 #define NEED_GETKEYSIGS 1
433 #include "keydb.c"