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