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