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