]> git.sommitrealweird.co.uk Git - onak.git/blob - keydb_db3.c
0ccaec33d63857fe0fdf9bc0320ef89078bf3550
[onak.git] / keydb_db3.c
1 /*
2  * keydb_db3.c - Routines to store and fetch keys in a DB3 database.
3  *
4  * Jonathan McDowell <noodles@earth.li>
5  *
6  * Copyright 2002 Project Purple
7  */
8
9 #include <assert.h>
10 #include <sys/types.h>
11 #include <sys/uio.h>
12 #include <ctype.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
19
20 #include <db.h>
21
22 #include "charfuncs.h"
23 #include "keydb.h"
24 #include "keyid.h"
25 #include "decodekey.h"
26 #include "keystructs.h"
27 #include "mem.h"
28 #include "onak-conf.h"
29 #include "parsekey.h"
30
31 /**
32  *      dbenv - our database environment.
33  */
34 static DB_ENV *dbenv = NULL;
35
36 /**
37  *      dbconn - our connection to the key database.
38  */
39 static DB *dbconn = NULL;
40
41 /**
42  *      worddb - our connection to the word database.
43  */
44 static DB *worddb = NULL;
45
46 /**
47  *      txn - our current transaction id.
48  */
49 static DB_TXN *txn = NULL;
50
51 /**
52  *      makewordlist - Takes a string and splits it into a set of unique words.
53  *      @wordlist: The current word list.
54  *      @words: The string to split and add.
55  *
56  *      We take words and split it on non alpha numeric characters. These get
57  *      added to the word list if they're not already present. If the wordlist
58  *      is NULL then we start a new list, otherwise it's search for already
59  *      added words. Note that words is modified in the process of scanning.
60  *
61  *      Returns the new word list.
62  */
63 struct ll *makewordlist(struct ll *wordlist, char *word)
64 {
65         char *start = NULL;
66         char *end = NULL;
67
68         /*
69          * Walk through the words string, spliting on non alphanumerics and
70          * then checking if the word already exists in the list. If not then
71          * we add it.
72          */
73         end = word;
74         while (end != NULL && *end != 0) {
75                 start = end;
76                 while (*start != 0 && !isalnum(*start)) {
77                         start++;
78                 }
79                 end = start;
80                 while (*end != 0 && isalnum(*end)) {
81                         *end = tolower(*end);
82                         end++;
83                 }
84                 if (end - start > 1) {
85                         if (*end != 0) {
86                                 *end = 0;
87                                 end++;
88                         }
89                         
90                         if (llfind(wordlist, start,
91                                         strcmp) == NULL) {
92                                 wordlist = lladd(wordlist,
93                                                 start);
94                         }
95                 }
96         }
97
98         return wordlist;
99 }
100
101 /**
102  *      initdb - Initialize the key database.
103  *
104  *      This function should be called before any of the other functions in
105  *      this file are called in order to allow the DB to be initialized ready
106  *      for access.
107  */
108 void initdb(void)
109 {
110         char buf[1024];
111         int ret = 0;
112
113         ret = db_env_create(&dbenv, 0);
114         if (ret != 0) {
115                 fprintf(stderr, "db_env_create: %s\n", db_strerror(ret));
116                 exit(1);
117         }
118
119         ret = dbenv->open(dbenv, config.db_dir,
120                         DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_LOCK |
121                         DB_INIT_TXN |
122                         DB_RECOVER | DB_CREATE,
123                         0);
124         if (ret != 0) {
125                 dbenv->err(dbenv, ret, "%s", config.db_dir);
126                 exit(1);
127         }
128
129         ret = db_create(&dbconn, dbenv, 0);
130         if (ret != 0) {
131                 fprintf(stderr, "db_create: %s\n", db_strerror(ret));
132                 exit(1);
133         }
134
135         ret = dbconn->open(dbconn, "keydb.db", 
136                         NULL,
137                         DB_HASH,
138                         DB_CREATE,
139                         0664);
140         if (ret != 0) {
141                 dbconn->err(dbconn, ret, "keydb.db");
142                 exit(1);
143         }
144
145         ret = db_create(&worddb, dbenv, 0);
146         if (ret != 0) {
147                 fprintf(stderr, "db_create: %s\n", db_strerror(ret));
148                 exit(1);
149         }
150         ret = worddb->set_flags(worddb, DB_DUP);
151
152         ret = worddb->open(worddb, "worddb", NULL, DB_BTREE,
153                         DB_CREATE,
154                         0664);
155         if (ret != 0) {
156                 worddb->err(worddb, ret, "worddb");
157                 exit(1);
158         }
159         
160         return;
161 }
162
163 /**
164  *      cleanupdb - De-initialize the key database.
165  *
166  *      This function should be called upon program exit to allow the DB to
167  *      cleanup after itself.
168  */
169 void cleanupdb(void)
170 {
171         worddb->close(worddb, 0);
172         worddb = NULL;
173         dbconn->close(dbconn, 0);
174         dbconn = NULL;
175         dbenv->close(dbenv, 0);
176         dbenv = NULL;
177 }
178
179 /**
180  *      starttrans - Start a transaction.
181  *
182  *      Start a transaction. Intended to be used if we're about to perform many
183  *      operations on the database to help speed it all up, or if we want
184  *      something to only succeed if all relevant operations are successful.
185  */
186 bool starttrans(void)
187 {
188         int ret;
189
190         assert(txn == NULL);
191
192         ret = txn_begin(dbenv,
193                 NULL, /* No parent transaction */
194                 &txn,
195                 0);
196         if (ret != 0) {
197                 dbenv->err(dbenv, ret, "starttrans():");
198                 exit(1);
199         }
200
201         return true;
202 }
203
204 /**
205  *      endtrans - End a transaction.
206  *
207  *      Ends a transaction.
208  */
209 void endtrans(void)
210 {
211         int ret;
212
213         assert(txn != NULL);
214
215         ret = txn_commit(txn,
216                 0);
217         if (ret != 0) {
218                 dbenv->err(dbenv, ret, "endtrans():");
219                 exit(1);
220         }
221         txn = NULL;
222
223         return;
224 }
225
226 /**
227  *      fetch_key - Given a keyid fetch the key from storage.
228  *      @keyid: The keyid to fetch.
229  *      @publickey: A pointer to a structure to return the key in.
230  *      @intrans: If we're already in a transaction.
231  *
232  *      We use the hex representation of the keyid as the filename to fetch the
233  *      key from. The key is stored in the file as a binary OpenPGP stream of
234  *      packets, so we can just use read_openpgp_stream() to read the packets
235  *      in and then parse_keys() to parse the packets into a publickey
236  *      structure.
237  */
238 int fetch_key(uint64_t keyid, struct openpgp_publickey **publickey,
239                 bool intrans)
240 {
241         struct openpgp_packet_list *packets = NULL;
242         DBT key, data;
243         int ret = 0;
244         int numkeys = 0;
245         struct buffer_ctx fetchbuf;
246
247         memset(&key, 0, sizeof(key));
248         memset(&data, 0, sizeof(data));
249
250         data.size = 0;
251         data.data = NULL;
252
253         key.size = sizeof(keyid);
254         key.data = &keyid;
255         keyid &= 0xFFFFFFFF;
256
257         if (!intrans) {
258                 starttrans();
259         }
260
261         ret = dbconn->get(dbconn,
262                         txn,
263                         &key,
264                         &data,
265                         0); /* flags*/
266         
267         if (ret == 0) {
268                 fetchbuf.buffer = data.data;
269                 fetchbuf.offset = 0;
270                 fetchbuf.size = data.size;
271                 read_openpgp_stream(buffer_fetchchar, &fetchbuf,
272                                 &packets);
273                 parse_keys(packets, publickey);
274                 free_packet_list(packets);
275                 packets = NULL;
276                 numkeys++;
277         } else if (ret != DB_NOTFOUND) {
278                 dbconn->err(dbconn, ret, "Problem retrieving key");
279         }
280
281         if (!intrans) {
282                 endtrans();
283         }
284
285         return (numkeys);
286 }
287
288 int worddb_cmp(const char *d1, const char *d2)
289 {
290         return memcmp(d1, d2, 12);
291 }
292
293 /**
294  *      fetch_key_text - Trys to find the keys that contain the supplied text.
295  *      @search: The text to search for.
296  *      @publickey: A pointer to a structure to return the key in.
297  *
298  *      This function searches for the supplied text and returns the keys that
299  *      contain it.
300  */
301 int fetch_key_text(const char *search, struct openpgp_publickey **publickey)
302 {
303         DBC *cursor = NULL;
304         DBT key, data;
305         int ret;
306         uint64_t keyid;
307         int i;
308         int numkeys;
309         char *searchtext = NULL;
310         struct ll *wordlist = NULL;
311         struct ll *curword = NULL;
312         struct ll *keylist = NULL;
313         struct ll *newkeylist = NULL;
314
315         numkeys = 0;
316         searchtext = strdup(search);
317         wordlist = makewordlist(wordlist, searchtext);
318
319         starttrans();
320
321         ret = worddb->cursor(worddb,
322                         txn,
323                         &cursor,
324                         0);   /* flags */
325
326         for (curword = wordlist; curword != NULL; curword = curword->next) {
327                 memset(&key, 0, sizeof(key));
328                 memset(&data, 0, sizeof(data));
329                 key.data = curword->object;
330                 key.size = strlen(curword->object);
331                 data.flags = DB_DBT_MALLOC;
332                 ret = cursor->c_get(cursor,
333                                 &key,
334                                 &data,
335                                 DB_SET);
336                 while (ret == 0 && strncmp(key.data, curword->object,
337                                         key.size) == 0 &&
338                                 ((char *) curword->object)[key.size] == 0) {
339                         keyid = 0;
340                         for (i = 4; i < 12; i++) {
341                                 keyid <<= 8;
342                                 keyid += ((unsigned char *)
343                                                 data.data)[i];
344                         }
345
346                         if (keylist == NULL ||
347                                         llfind(keylist, data.data,
348                                                 worddb_cmp) != NULL) {
349                                 newkeylist = lladd(newkeylist, data.data);
350                                 data.data = NULL;
351                         } else {
352                                 free(data.data);
353                                 data.data = NULL;
354                         }
355                         ret = cursor->c_get(cursor,
356                                         &key,
357                                         &data,
358                                         DB_NEXT);
359                 }
360                 llfree(keylist, free);
361                 keylist = newkeylist;
362                 newkeylist = NULL;
363                 if (data.data != NULL) {
364                         free(data.data);
365                         data.data = NULL;
366                 }
367         }
368         llfree(wordlist, NULL);
369         wordlist = NULL;
370         
371         for (newkeylist = keylist;
372                         newkeylist != NULL && numkeys < config.maxkeys;
373                         newkeylist = newkeylist->next) {
374
375                         keyid = 0;
376                         for (i = 4; i < 12; i++) {
377                                 keyid <<= 8;
378                                 keyid += ((unsigned char *)
379                                                 newkeylist->object)[i];
380                         }
381
382                         numkeys += fetch_key(keyid,
383                                         publickey,
384                                         true);
385         }
386         llfree(keylist, free);
387         keylist = NULL;
388         free(searchtext);
389         searchtext = NULL;
390
391         ret = cursor->c_close(cursor);
392         cursor = NULL;
393
394         endtrans();
395         
396         return (numkeys);
397 }
398
399 /**
400  *      store_key - Takes a key and stores it.
401  *      @publickey: A pointer to the public key to store.
402  *      @intrans: If we're already in a transaction.
403  *      @update: If true the key exists and should be updated.
404  *
405  *      Again we just use the hex representation of the keyid as the filename
406  *      to store the key to. We flatten the public key to a list of OpenPGP
407  *      packets and then use write_openpgp_stream() to write the stream out to
408  *      the file. If update is true then we delete the old key first, otherwise
409  *      we trust that it doesn't exist.
410  */
411 int store_key(struct openpgp_publickey *publickey, bool intrans, bool update)
412 {
413         struct     openpgp_packet_list *packets = NULL;
414         struct     openpgp_packet_list *list_end = NULL;
415         struct     openpgp_publickey *next = NULL;
416         int        ret = 0;
417         int        i = 0;
418         struct     buffer_ctx storebuf;
419         DBT        key;
420         DBT        data;
421         uint64_t   keyid = 0;
422         char     **uids = NULL;
423         char      *primary = NULL;
424         unsigned char worddb_data[12];
425         struct ll *wordlist = NULL;
426         struct ll *curword  = NULL;
427
428         keyid = get_keyid(publickey);
429
430         if (!intrans) {
431                 starttrans();
432         }
433
434         /*
435          * Delete the key if we already have it.
436          *
437          * TODO: Can we optimize this perhaps? Possibly when other data is
438          * involved as well? I suspect this is easiest and doesn't make a lot
439          * of difference though - the largest chunk of data is the keydata and
440          * it definitely needs updated.
441          */
442         if (update) {
443                 delete_key(keyid, true);
444         }
445
446         /*
447          * Convert the key to a flat set of binary data.
448          */
449         next = publickey->next;
450         publickey->next = NULL;
451         flatten_publickey(publickey, &packets, &list_end);
452         publickey->next = next;
453
454         storebuf.offset = 0; 
455         storebuf.size = 8192;
456         storebuf.buffer = malloc(8192);
457         
458         write_openpgp_stream(buffer_putchar, &storebuf, packets);
459
460         /*
461          * Now we have the key data store it in the DB; the keyid is the key.
462          */
463         memset(&key, 0, sizeof(key));
464         memset(&data, 0, sizeof(data));
465         key.data = &keyid;
466         key.size = sizeof(keyid);
467         keyid &= 0xFFFFFFFF;
468         data.size = storebuf.offset;
469         data.data = storebuf.buffer;
470
471         ret = dbconn->put(dbconn,
472                         txn,
473                         &key,
474                         &data,
475                         0); /* flags*/
476         if (ret != 0) {
477                 dbconn->err(dbconn, ret, "Problem storing key");
478         }
479
480         free(storebuf.buffer);
481         storebuf.buffer = NULL;
482         storebuf.size = 0;
483         storebuf.offset = 0; 
484
485         free_packet_list(packets);
486         packets = NULL;
487
488         /*
489          * Walk through our uids storing the words into the db with the keyid.
490          */
491         uids = keyuids(publickey, &primary);
492         if (uids != NULL) {
493                 for (i = 0; ret == 0 && uids[i] != NULL; i++) {
494                         wordlist = makewordlist(wordlist, uids[i]);
495                 }
496
497                 for (curword = wordlist; curword != NULL;
498                                 curword = curword->next) {
499                         memset(&key, 0, sizeof(key));
500                         memset(&data, 0, sizeof(data));
501                         key.data = curword->object;
502                         key.size = strlen(key.data);
503                         data.data = worddb_data;
504                         data.size = sizeof(worddb_data);
505
506                         /*
507                          * Our data is the key creation time followed by the
508                          * key id.
509                          */
510                         worddb_data[ 0] = publickey->publickey->data[1];
511                         worddb_data[ 1] = publickey->publickey->data[2];
512                         worddb_data[ 2] = publickey->publickey->data[3];
513                         worddb_data[ 3] = publickey->publickey->data[4];
514                         worddb_data[ 4] = (keyid >> 56) & 0xFF;
515                         worddb_data[ 5] = (keyid >> 48) & 0xFF;
516                         worddb_data[ 6] = (keyid >> 40) & 0xFF;
517                         worddb_data[ 7] = (keyid >> 32) & 0xFF;
518                         worddb_data[ 8] = (keyid >> 24) & 0xFF;
519                         worddb_data[ 9] = (keyid >> 16) & 0xFF;
520                         worddb_data[10] = (keyid >>  8) & 0xFF;
521                         worddb_data[11] = keyid & 0xFF; 
522                         ret = worddb->put(worddb,
523                                 txn,
524                                 &key,
525                                 &data,
526                                 0);
527                         if (ret != 0) {
528                                 worddb->err(worddb, ret,
529                                         "Problem storing key");
530                         }
531                 }
532
533                 /*
534                  * Free our UID and word lists.
535                  */
536                 llfree(wordlist, NULL);
537                 for (i = 0; uids[i] != NULL; i++) {
538                         free(uids[i]);
539                         uids[i] = NULL;
540                 }
541                 free(uids);
542                 uids = NULL;
543         }
544
545         if (!intrans) {
546                 endtrans();
547         }
548
549         return 0;
550 }
551
552 /**
553  *      delete_key - Given a keyid delete the key from storage.
554  *      @keyid: The keyid to delete.
555  *      @intrans: If we're already in a transaction.
556  *
557  *      This function deletes a public key from whatever storage mechanism we
558  *      are using. Returns 0 if the key existed.
559  */
560 int delete_key(uint64_t keyid, bool intrans)
561 {
562         struct openpgp_publickey *publickey = NULL;
563         DBT key, data;
564         DBC *cursor = NULL;
565         int ret = 0;
566         int i;
567         char **uids = NULL;
568         char *primary = NULL;
569         unsigned char worddb_data[12];
570         struct ll *wordlist = NULL;
571         struct ll *curword  = NULL;
572
573         keyid &= 0xFFFFFFFF;
574
575         if (!intrans) {
576                 starttrans();
577         }
578
579         fetch_key(keyid, &publickey, true);
580
581         /*
582          * Walk through the uids removing the words from the worddb.
583          */
584         if (publickey != NULL) {
585                 uids = keyuids(publickey, &primary);
586         }
587         if (uids != NULL) {
588                 for (i = 0; ret == 0 && uids[i] != NULL; i++) {
589                         wordlist = makewordlist(wordlist, uids[i]);
590                 }
591                                 
592                 ret = worddb->cursor(worddb,
593                         txn,
594                         &cursor,
595                         0);   /* flags */
596
597                 for (curword = wordlist; curword != NULL;
598                                 curword = curword->next) {
599                         memset(&key, 0, sizeof(key));
600                         memset(&data, 0, sizeof(data));
601                         key.data = curword->object;
602                         key.size = strlen(key.data);
603                         data.data = worddb_data;
604                         data.size = sizeof(worddb_data);
605
606                         /*
607                          * Our data is the key creation time followed by the
608                          * key id.
609                          */
610                         worddb_data[ 0] = publickey->publickey->data[1];
611                         worddb_data[ 1] = publickey->publickey->data[2];
612                         worddb_data[ 2] = publickey->publickey->data[3];
613                         worddb_data[ 3] = publickey->publickey->data[4];
614                         worddb_data[ 4] = (keyid >> 56) & 0xFF;
615                         worddb_data[ 5] = (keyid >> 48) & 0xFF;
616                         worddb_data[ 6] = (keyid >> 40) & 0xFF;
617                         worddb_data[ 7] = (keyid >> 32) & 0xFF;
618                         worddb_data[ 8] = (keyid >> 24) & 0xFF;
619                         worddb_data[ 9] = (keyid >> 16) & 0xFF;
620                         worddb_data[10] = (keyid >>  8) & 0xFF;
621                         worddb_data[11] = keyid & 0xFF; 
622
623                         ret = cursor->c_get(cursor,
624                                 &key,
625                                 &data,
626                                 DB_GET_BOTH);
627
628                         if (ret == 0) {
629                                 ret = cursor->c_del(cursor, 0);
630                                 if (ret != 0) {
631                                         worddb->err(worddb, ret,
632                                                 "Problem deleting word.");
633                                 }
634                         }
635
636                         if (ret != 0) {
637                                 worddb->err(worddb, ret,
638                                         "Problem deleting word.");
639                         }
640                 }
641                 ret = cursor->c_close(cursor);
642                 cursor = NULL;
643
644                 /*
645                  * Free our UID and word lists.
646                  */
647                 llfree(wordlist, NULL);
648                 for (i = 0; uids[i] != NULL; i++) {
649                         free(uids[i]);
650                         uids[i] = NULL;
651                 }
652                 free(uids);
653                 uids = NULL;
654                 free_publickey(publickey);
655                 publickey = NULL;
656         }
657
658         key.data = &keyid;
659         key.size = sizeof(keyid);
660
661         dbconn->del(dbconn,
662                         txn,
663                         &key,
664                         0); /* flags */
665
666         if (!intrans) {
667                 endtrans();
668         }
669
670         return (ret == DB_NOTFOUND);
671 }
672
673 /*
674  * Include the basic keydb routines.
675  */
676 #define NEED_GETFULLKEYID 1
677 #define NEED_GETKEYSIGS 1
678 #define NEED_KEYID2UID 1
679 #include "keydb.c"