869aa3d9f9a3cf301e8d1de60fa86a0fbc38616f
[oweals/gnunet.git] / src / namestore / plugin_namestore_flat.c
1  /*
2   * This file is part of GNUnet
3   * Copyright (C) 2009-2015 GNUnet e.V.
4   *
5   * GNUnet is free software; you can redistribute it and/or modify
6   * it under the terms of the GNU General Public License as published
7   * by the Free Software Foundation; either version 3, or (at your
8   * option) any later version.
9   *
10   * GNUnet is distributed in the hope that it will be useful, but
11   * WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with GNUnet; see the file COPYING.  If not, write to the
17   * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18   * Boston, MA 02110-1301, USA.
19   */
20
21 /**
22  * @file namestore/plugin_namestore_flat.c
23  * @brief file-based namestore backend
24  * @author Martin Schanzenbach
25  */
26
27 #include "platform.h"
28 #include "gnunet_namestore_plugin.h"
29 #include "gnunet_namestore_service.h"
30 #include "gnunet_gnsrecord_lib.h"
31 #include "namestore.h"
32
33 /**
34  * Context for all functions in this plugin.
35  */
36 struct Plugin
37 {
38
39   const struct GNUNET_CONFIGURATION_Handle *cfg;
40
41   /**
42    * Database filename.
43    */
44   char *fn;
45
46   /**
47    * HashMap
48    */
49   struct GNUNET_CONTAINER_MultiHashMap *hm;
50
51   /**
52    * Offset
53    */
54   uint32_t offset;
55
56   /**
57    * Target Offset
58    */
59   uint32_t target_offset;
60
61   /**
62    * Iterator closure
63    */
64   void *iter_cls;
65
66   /**
67    * Iterator
68    */
69   GNUNET_NAMESTORE_RecordIterator iter;
70
71   /**
72    * Zone to iterate
73    */
74   const struct GNUNET_CRYPTO_EcdsaPrivateKey *iter_zone;
75
76   /**
77    * PKEY to look for in zone to name
78    */
79   struct GNUNET_CRYPTO_EcdsaPublicKey *iter_pkey;
80
81   /**
82    * Iteration result found
83    */
84   int iter_result_found;
85
86 };
87
88 struct FlatFileEntry
89 {
90   /**
91    * Entry zone
92    */
93   struct GNUNET_CRYPTO_EcdsaPrivateKey *private_key;
94
95   /**
96    * Record cound
97    */
98   uint32_t record_count;
99
100   /**
101    * Rvalue
102    */
103   uint64_t rvalue;
104
105   /**
106    * Record data
107    */
108   struct GNUNET_GNSRECORD_Data *record_data;
109
110   /**
111    * Label
112    */
113   char *label;
114
115
116 };
117
118
119 /**
120  * Initialize the database connections and associated
121  * data structures (create tables and indices
122  * as needed as well).
123  *
124  * @param plugin the plugin context (state for this module)
125  * @return #GNUNET_OK on success
126  */
127 static int
128 database_setup (struct Plugin *plugin)
129 {
130   char *afsdir;
131   char *key;
132   char *record_data;
133   char *zone_private_key;
134   char *record_data_b64;
135   char *buffer;
136   char *line;
137   char *label;
138   char *rvalue;
139   char *record_count;
140   size_t record_data_size;
141   size_t size;
142   size_t key_len;
143   struct GNUNET_HashCode hkey;
144   struct GNUNET_DISK_FileHandle *fh;
145   struct FlatFileEntry *entry;
146
147   if (GNUNET_OK !=
148       GNUNET_CONFIGURATION_get_value_filename (plugin->cfg,
149                                                "namestore-flat",
150                                                "FILENAME", &afsdir))
151   {
152     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
153                                "namestore-flat", "FILENAME");
154     return GNUNET_SYSERR;
155   }
156   if (GNUNET_OK != GNUNET_DISK_file_test (afsdir))
157   {
158     if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (afsdir))
159     {
160       GNUNET_break (0);
161       GNUNET_free (afsdir);
162       return GNUNET_SYSERR;
163     }
164   }
165   /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
166   plugin->fn = afsdir;
167
168   /* Load data from file into hashmap */
169   plugin->hm = GNUNET_CONTAINER_multihashmap_create (10,
170                                                      GNUNET_NO);
171   fh = GNUNET_DISK_file_open (afsdir,
172                               GNUNET_DISK_OPEN_CREATE |
173                               GNUNET_DISK_OPEN_READWRITE,
174                               GNUNET_DISK_PERM_USER_WRITE |
175                               GNUNET_DISK_PERM_USER_READ);
176   if (NULL == fh)
177   {
178     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
179          _("Unable to initialize file: %s.\n"),
180          afsdir);
181     return GNUNET_SYSERR;
182   }
183   if (GNUNET_SYSERR ==
184       GNUNET_DISK_file_size (afsdir,
185                              &size,
186                              GNUNET_YES,
187                              GNUNET_YES))
188   {
189     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
190          _("Unable to get filesize: %s.\n"),
191          afsdir);
192     return GNUNET_SYSERR;
193   }
194
195   buffer = GNUNET_malloc (size) + 1;
196   if (GNUNET_SYSERR ==
197       GNUNET_DISK_file_read (fh,
198                              buffer,
199                              size))
200   {
201     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
202          _("Unable to read file: %s.\n"),
203          afsdir);
204     return GNUNET_SYSERR;
205   }
206   GNUNET_DISK_file_close (fh);
207
208   if (0 < size)
209   {
210     line = strtok (buffer, "\n");
211     while (line != NULL) {
212       zone_private_key = strtok (line, ",");
213       if (NULL == zone_private_key)
214         break;
215       rvalue = strtok (NULL, ",");
216       if (NULL == rvalue)
217         break;
218       record_count = strtok (NULL, ",");
219       if (NULL == record_count)
220         break;
221       record_data_b64 = strtok (NULL, ",");
222       if (NULL == record_data_b64)
223         break;
224       label = strtok (NULL, ",");
225       if (NULL == label)
226         break;
227       line = strtok (NULL, "\n");
228       entry = GNUNET_malloc (sizeof (struct FlatFileEntry));
229       if (1 != sscanf (rvalue, "%lu", &entry->rvalue))
230       {
231         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
232                     "Error parsing entry\n");
233         GNUNET_free (entry);
234         break;
235       }
236       if (1 != sscanf (record_count, "%u", &entry->record_count))
237       {
238         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
239                     "Error parsing entry\n");
240         GNUNET_free (entry);
241         break;
242       }
243       entry->label = GNUNET_strdup (label);
244       record_data_size = GNUNET_STRINGS_base64_decode (record_data_b64,
245                                                        strlen (record_data_b64),
246                                                        &record_data);
247       entry->record_data =
248         GNUNET_malloc (sizeof (struct GNUNET_GNSRECORD_Data) * entry->record_count);
249       if (GNUNET_OK != GNUNET_GNSRECORD_records_deserialize (record_data_size,
250                                                              record_data,
251                                                              entry->record_count,
252                                                              entry->record_data))
253       {
254         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
255                     "Unable to deserialize record %s\n", label);
256         GNUNET_free (entry->label);
257         GNUNET_free (entry);
258         GNUNET_free (record_data);
259         break;
260       }
261       GNUNET_free (record_data);
262       GNUNET_STRINGS_base64_decode (zone_private_key,
263                                     strlen (zone_private_key),
264                                     (char**)&entry->private_key);
265       key_len = strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
266       key = GNUNET_malloc (strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
267       GNUNET_memcpy (key, label, strlen (label));
268       GNUNET_memcpy (key+strlen(label),
269               entry->private_key,
270               sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
271       GNUNET_CRYPTO_hash (key,
272                           key_len,
273                           &hkey);
274       GNUNET_free (key);
275       if (GNUNET_OK !=
276           GNUNET_CONTAINER_multihashmap_put (plugin->hm,
277                                              &hkey,
278                                              entry,
279                                              GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
280       {
281         GNUNET_free (entry);
282         GNUNET_break (0);
283       }
284     }
285   }
286   GNUNET_free (buffer);
287   return GNUNET_OK;
288 }
289
290
291 /**
292  * Store values in hashmap in file and free data
293  *
294  * @param plugin the plugin context
295  */
296 static int
297 store_and_free_entries (void *cls,
298                         const struct GNUNET_HashCode *key,
299                         void *value)
300 {
301   struct GNUNET_DISK_FileHandle *fh = cls;
302   struct FlatFileEntry *entry = value;
303   char *line;
304   char *zone_private_key;
305   char *record_data_b64;
306   size_t data_size;
307
308   GNUNET_STRINGS_base64_encode ((char*)entry->private_key,
309                                 sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey),
310                                 &zone_private_key);
311   data_size = GNUNET_GNSRECORD_records_get_size (entry->record_count,
312                                                  entry->record_data);
313   {
314     char data[data_size];
315
316     if (data_size !=
317         GNUNET_GNSRECORD_records_serialize (entry->record_count,
318                                             entry->record_data,
319                                             data_size,
320                                             data))
321     {
322       GNUNET_break (0);
323       GNUNET_free (zone_private_key);
324       return GNUNET_SYSERR;
325     }
326     GNUNET_STRINGS_base64_encode (data,
327                                   data_size,
328                                   &record_data_b64);
329   }
330   GNUNET_asprintf (&line,
331                    "%s,%lu,%u,%s,%s\n",
332                    zone_private_key,
333                    entry->rvalue,
334                    entry->record_count,
335                    record_data_b64,
336                    entry->label);
337   GNUNET_free (record_data_b64);
338   GNUNET_free (zone_private_key);
339
340   GNUNET_DISK_file_write (fh,
341                           line,
342                           strlen (line));
343
344   GNUNET_free (entry->private_key);
345   GNUNET_free (entry->label);
346   GNUNET_free (entry->record_data);
347   GNUNET_free (entry);
348   return GNUNET_YES;
349 }
350
351
352 /**
353  * Shutdown database connection and associate data
354  * structures.
355  * @param plugin the plugin context (state for this module)
356  */
357 static void
358 database_shutdown (struct Plugin *plugin)
359 {
360   struct GNUNET_DISK_FileHandle *fh;
361   fh = GNUNET_DISK_file_open (plugin->fn,
362                               GNUNET_DISK_OPEN_CREATE |
363                               GNUNET_DISK_OPEN_TRUNCATE |
364                               GNUNET_DISK_OPEN_READWRITE,
365                               GNUNET_DISK_PERM_USER_WRITE |
366                               GNUNET_DISK_PERM_USER_READ);
367   if (NULL == fh)
368   {
369     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
370                 _("Unable to initialize file: %s.\n"),
371                 plugin->fn);
372     return;
373   }
374
375   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
376                                          &store_and_free_entries,
377                                          fh);
378   GNUNET_CONTAINER_multihashmap_destroy (plugin->hm);
379   GNUNET_DISK_file_close (fh);
380 }
381
382
383 /**
384  * Store a record in the datastore.  Removes any existing record in the
385  * same zone with the same name.
386  *
387  * @param cls closure (internal context for the plugin)
388  * @param zone_key private key of the zone
389  * @param label name that is being mapped (at most 255 characters long)
390  * @param rd_count number of entries in @a rd array
391  * @param rd array of records with data to store
392  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
393  */
394 static int
395 namestore_store_records (void *cls,
396                          const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
397                          const char *label,
398                          unsigned int rd_count,
399                          const struct GNUNET_GNSRECORD_Data *rd)
400 {
401   struct Plugin *plugin = cls;
402   uint64_t rvalue;
403   size_t key_len;
404   char *key;
405   struct GNUNET_HashCode hkey;
406   struct FlatFileEntry *entry;
407   int i;
408
409   rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX);
410   key_len = strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
411   key = GNUNET_malloc (key_len);
412   GNUNET_memcpy (key, label, strlen (label));
413   GNUNET_memcpy (key+strlen(label),
414           zone_key,
415           sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
416   GNUNET_CRYPTO_hash (key,
417                       key_len,
418                       &hkey);
419
420   GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm, &hkey);
421
422   if (0 < rd_count)
423   {
424     entry = GNUNET_malloc (sizeof (struct FlatFileEntry));
425     entry->private_key = GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
426     GNUNET_asprintf (&entry->label,
427                      label,
428                      strlen (label));
429     GNUNET_memcpy (entry->private_key,
430             zone_key,
431             sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
432     entry->rvalue = rvalue;
433     entry->record_count = rd_count;
434     entry->record_data = GNUNET_malloc (sizeof (struct GNUNET_GNSRECORD_Data) * rd_count);
435     for (i = 0; i < rd_count; i++)
436     {
437       entry->record_data[i].expiration_time = rd[i].expiration_time;
438       entry->record_data[i].record_type = rd[i].record_type;
439       entry->record_data[i].flags = rd[i].flags;
440       entry->record_data[i].data_size = rd[i].data_size;
441       entry->record_data[i].data = GNUNET_malloc (rd[i].data_size);
442       GNUNET_memcpy ((char*)entry->record_data[i].data, rd[i].data, rd[i].data_size);
443     }
444     return GNUNET_CONTAINER_multihashmap_put (plugin->hm,
445                                               &hkey,
446                                               entry,
447                                               GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
448   }
449   return GNUNET_NO;
450 }
451
452
453 /**
454  * Lookup records in the datastore for which we are the authority.
455  *
456  * @param cls closure (internal context for the plugin)
457  * @param zone private key of the zone
458  * @param label name of the record in the zone
459  * @param iter function to call with the result
460  * @param iter_cls closure for @a iter
461  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
462  */
463 static int
464 namestore_lookup_records (void *cls,
465                           const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
466                           const char *label,
467                           GNUNET_NAMESTORE_RecordIterator iter,
468                           void *iter_cls)
469 {
470   struct Plugin *plugin = cls;
471   struct FlatFileEntry *entry;
472   struct GNUNET_HashCode hkey;
473   char *key;
474   size_t key_len;
475
476   if (NULL == zone)
477   {
478     return GNUNET_SYSERR;
479   }
480   key_len = strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
481   key = GNUNET_malloc (key_len);
482   GNUNET_memcpy (key, label, strlen (label));
483   GNUNET_memcpy (key+strlen(label),
484           zone,
485           sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
486   GNUNET_CRYPTO_hash (key,
487                       key_len,
488                       &hkey);
489   GNUNET_free (key);
490
491   entry = GNUNET_CONTAINER_multihashmap_get (plugin->hm, &hkey);
492
493   if (NULL == entry)
494     return GNUNET_NO;
495   if (NULL != iter)
496     iter (iter_cls, entry->private_key, entry->label, entry->record_count, entry->record_data);
497   return GNUNET_YES;
498 }
499
500
501 static int
502 iterate_zones (void *cls,
503                const struct GNUNET_HashCode *key,
504                void *value)
505 {
506   struct Plugin *plugin = cls;
507   struct FlatFileEntry *entry = value;
508
509   if ((plugin->target_offset > plugin->offset) ||
510       ( (NULL != plugin->iter_zone) &&
511         (0 != memcmp (entry->private_key,
512                       plugin->iter_zone,
513                       sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey))))) {
514     plugin->offset++;
515     return GNUNET_YES;
516   }
517   plugin->iter (plugin->iter_cls,
518                 entry->private_key,
519                 entry->label,
520                 entry->record_count,
521                 entry->record_data);
522   plugin->iter_result_found = GNUNET_YES;
523   return GNUNET_NO;
524 }
525
526 /**
527  * Iterate over the results for a particular key and zone in the
528  * datastore.  Will return at most one result to the iterator.
529  *
530  * @param cls closure (internal context for the plugin)
531  * @param zone hash of public key of the zone, NULL to iterate over all zones
532  * @param offset offset in the list of all matching records
533  * @param iter function to call with the result
534  * @param iter_cls closure for @a iter
535  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
536  */
537 static int
538 namestore_iterate_records (void *cls,
539                            const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
540                            uint64_t offset,
541                            GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
542 {
543   struct Plugin *plugin = cls;
544   plugin->target_offset = offset;
545   plugin->offset = 0;
546   plugin->iter = iter;
547   plugin->iter_cls = iter_cls;
548   plugin->iter_zone = zone;
549   plugin->iter_result_found = GNUNET_NO;
550   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
551                                          &iterate_zones,
552                                          plugin);
553   return plugin->iter_result_found;
554 }
555
556 static int
557 zone_to_name (void *cls,
558               const struct GNUNET_HashCode *key,
559               void *value)
560 {
561   struct Plugin *plugin = cls;
562   struct FlatFileEntry *entry = value;
563   int i;
564
565   if (0 != memcmp (entry->private_key,
566                    plugin->iter_zone,
567                    sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)))
568     return GNUNET_YES;
569
570   for (i = 0; i < entry->record_count; i++) {
571     if (GNUNET_GNSRECORD_TYPE_PKEY != entry->record_data[i].record_type)
572       continue;
573     if (0 == memcmp (plugin->iter_pkey,
574                      entry->record_data[i].data,
575                      sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)))
576     {
577       plugin->iter (plugin->iter_cls,
578                     entry->private_key,
579                     entry->label,
580                     entry->record_count,
581                     entry->record_data);
582       plugin->iter_result_found = GNUNET_YES;
583
584     }
585   }
586
587   return GNUNET_YES;
588 }
589
590 /**
591  * Look for an existing PKEY delegation record for a given public key.
592  * Returns at most one result to the iterator.
593  *
594  * @param cls closure (internal context for the plugin)
595  * @param zone private key of the zone to look up in, never NULL
596  * @param value_zone public key of the target zone (value), never NULL
597  * @param iter function to call with the result
598  * @param iter_cls closure for @a iter
599  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
600  */
601 static int
602 namestore_zone_to_name (void *cls,
603                         const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
604                         const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone,
605                         GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
606 {
607   struct Plugin *plugin = cls;
608
609   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
610               "Performing reverse lookup for `%s'\n",
611               GNUNET_GNSRECORD_z2s (value_zone));
612
613   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
614                                          &zone_to_name,
615                                          plugin);
616
617
618   return plugin->iter_result_found;
619 }
620
621
622 /**
623  * Entry point for the plugin.
624  *
625  * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
626  * @return NULL on error, otherwise the plugin context
627  */
628 void *
629 libgnunet_plugin_namestore_flat_init (void *cls)
630 {
631   static struct Plugin plugin;
632   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
633   struct GNUNET_NAMESTORE_PluginFunctions *api;
634
635   if (NULL != plugin.cfg)
636     return NULL;                /* can only initialize once! */
637   memset (&plugin, 0, sizeof (struct Plugin));
638   plugin.cfg = cfg;
639   if (GNUNET_OK != database_setup (&plugin))
640   {
641     database_shutdown (&plugin);
642     return NULL;
643   }
644   api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
645   api->cls = &plugin;
646   api->store_records = &namestore_store_records;
647   api->iterate_records = &namestore_iterate_records;
648   api->zone_to_name = &namestore_zone_to_name;
649   api->lookup_records = &namestore_lookup_records;
650   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
651               _("flat file database running\n"));
652   return api;
653 }
654
655
656 /**
657  * Exit point from the plugin.
658  *
659  * @param cls the plugin context (as returned by "init")
660  * @return always NULL
661  */
662 void *
663 libgnunet_plugin_namestore_flat_done (void *cls)
664 {
665   struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
666   struct Plugin *plugin = api->cls;
667
668   database_shutdown (plugin);
669   plugin->cfg = NULL;
670   GNUNET_free (api);
671   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
672               "flat file plugin is finished\n");
673   return NULL;
674 }
675
676 /* end of plugin_namestore_flat.c */