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