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