2 * This file is part of GNUnet
3 * Copyright (C) 2009-2015, 2018 GNUnet e.V.
5 * GNUnet is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Affero General Public License as published
7 * by the Free Software Foundation, either version 3 of the License,
8 * or (at your option) any later version.
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 * Affero General Public License for more details.
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 * @file namestore/plugin_namestore_flat.c
21 * @brief file-based namestore backend
22 * @author Martin Schanzenbach
23 * @author Christian Grothoff
27 #include "gnunet_namestore_plugin.h"
28 #include "gnunet_namestore_service.h"
29 #include "gnunet_gnsrecord_lib.h"
30 #include "namestore.h"
33 * Context for all functions in this plugin.
38 const struct GNUNET_CONFIGURATION_Handle *cfg;
48 struct GNUNET_CONTAINER_MultiHashMap *hm;
58 struct GNUNET_CRYPTO_EcdsaPrivateKey *private_key;
63 uint32_t record_count;
73 struct GNUNET_GNSRECORD_Data *record_data;
85 * Initialize the database connections and associated
86 * data structures (create tables and indices
89 * @param plugin the plugin context (state for this module)
90 * @return #GNUNET_OK on success
93 database_setup (struct Plugin *plugin)
98 char *zone_private_key;
99 char *record_data_b64;
105 size_t record_data_size;
108 struct GNUNET_HashCode hkey;
109 struct GNUNET_DISK_FileHandle *fh;
110 struct FlatFileEntry *entry;
113 GNUNET_CONFIGURATION_get_value_filename (plugin->cfg,
118 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
121 return GNUNET_SYSERR;
124 GNUNET_DISK_file_test (afsdir))
127 GNUNET_DISK_directory_create_for_file (afsdir))
130 GNUNET_free (afsdir);
131 return GNUNET_SYSERR;
134 /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
137 /* Load data from file into hashmap */
138 plugin->hm = GNUNET_CONTAINER_multihashmap_create (10,
140 fh = GNUNET_DISK_file_open (afsdir,
141 GNUNET_DISK_OPEN_CREATE |
142 GNUNET_DISK_OPEN_READWRITE,
143 GNUNET_DISK_PERM_USER_WRITE |
144 GNUNET_DISK_PERM_USER_READ);
147 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
148 _("Unable to initialize file: %s.\n"),
150 return GNUNET_SYSERR;
153 GNUNET_DISK_file_size (afsdir,
158 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
159 _("Unable to get filesize: %s.\n"),
161 GNUNET_DISK_file_close (fh);
162 return GNUNET_SYSERR;
165 buffer = GNUNET_malloc (size + 1);
167 GNUNET_DISK_file_read (fh,
171 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
172 _("Unable to read file: %s.\n"),
174 GNUNET_free (buffer);
175 GNUNET_DISK_file_close (fh);
176 return GNUNET_SYSERR;
179 GNUNET_DISK_file_close (fh);
183 line = strtok (buffer, "\n");
186 zone_private_key = strtok (line, ",");
187 if (NULL == zone_private_key)
189 rvalue = strtok (NULL, ",");
192 record_count = strtok (NULL, ",");
193 if (NULL == record_count)
195 record_data_b64 = strtok (NULL, ",");
196 if (NULL == record_data_b64)
198 label = strtok (NULL, ",");
201 line = strtok (NULL, "\n");
202 entry = GNUNET_new (struct FlatFileEntry);
204 unsigned long long ll;
206 if (1 != sscanf (rvalue,
210 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
211 "Error parsing entry\n");
215 entry->rvalue = (uint64_t) ll;
220 if (1 != sscanf (record_count,
224 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
225 "Error parsing entry\n");
229 entry->record_count = (uint32_t) ui;
231 entry->label = GNUNET_strdup (label);
233 = GNUNET_STRINGS_base64_decode (record_data_b64,
234 strlen (record_data_b64),
237 GNUNET_new_array (entry->record_count,
238 struct GNUNET_GNSRECORD_Data);
240 GNUNET_GNSRECORD_records_deserialize (record_data_size,
245 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
246 "Unable to deserialize record %s\n",
248 GNUNET_free (entry->label);
250 GNUNET_free (record_data);
253 GNUNET_free (record_data);
254 GNUNET_STRINGS_base64_decode (zone_private_key,
255 strlen (zone_private_key),
256 (char**)&entry->private_key);
257 key_len = strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
258 key = GNUNET_malloc (strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
262 GNUNET_memcpy (key+strlen(label),
264 sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
265 GNUNET_CRYPTO_hash (key,
270 GNUNET_CONTAINER_multihashmap_put (plugin->hm,
273 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
280 GNUNET_free (buffer);
286 * Store values in hashmap in file and free data
288 * @param plugin the plugin context
289 * @param key key in the map
290 * @param value a `struct FlatFileEntry`
293 store_and_free_entries (void *cls,
294 const struct GNUNET_HashCode *key,
297 struct GNUNET_DISK_FileHandle *fh = cls;
298 struct FlatFileEntry *entry = value;
300 char *zone_private_key;
301 char *record_data_b64;
305 GNUNET_STRINGS_base64_encode ((char*)entry->private_key,
306 sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey),
308 data_size = GNUNET_GNSRECORD_records_get_size (entry->record_count,
313 GNUNET_free (zone_private_key);
314 return GNUNET_SYSERR;
316 if (data_size >= UINT16_MAX)
319 GNUNET_free (zone_private_key);
320 return GNUNET_SYSERR;
323 char data[data_size];
326 ret = GNUNET_GNSRECORD_records_serialize (entry->record_count,
334 GNUNET_free (zone_private_key);
335 return GNUNET_SYSERR;
337 GNUNET_STRINGS_base64_encode (data,
341 GNUNET_asprintf (&line,
342 "%s,%llu,%u,%s,%s\n",
344 (unsigned long long) entry->rvalue,
345 (unsigned int) entry->record_count,
348 GNUNET_free (record_data_b64);
349 GNUNET_free (zone_private_key);
351 GNUNET_DISK_file_write (fh,
356 GNUNET_free (entry->private_key);
357 GNUNET_free (entry->label);
358 GNUNET_free (entry->record_data);
365 * Shutdown database connection and associate data
367 * @param plugin the plugin context (state for this module)
370 database_shutdown (struct Plugin *plugin)
372 struct GNUNET_DISK_FileHandle *fh;
374 fh = GNUNET_DISK_file_open (plugin->fn,
375 GNUNET_DISK_OPEN_CREATE |
376 GNUNET_DISK_OPEN_TRUNCATE |
377 GNUNET_DISK_OPEN_READWRITE,
378 GNUNET_DISK_PERM_USER_WRITE |
379 GNUNET_DISK_PERM_USER_READ);
382 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
383 _("Unable to initialize file: %s.\n"),
388 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
389 &store_and_free_entries,
391 GNUNET_CONTAINER_multihashmap_destroy (plugin->hm);
392 GNUNET_DISK_file_close (fh);
397 * Store a record in the datastore. Removes any existing record in the
398 * same zone with the same name.
400 * @param cls closure (internal context for the plugin)
401 * @param zone_key private key of the zone
402 * @param label name that is being mapped (at most 255 characters long)
403 * @param rd_count number of entries in @a rd array
404 * @param rd array of records with data to store
405 * @return #GNUNET_OK on success, else #GNUNET_SYSERR
408 namestore_flat_store_records (void *cls,
409 const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
411 unsigned int rd_count,
412 const struct GNUNET_GNSRECORD_Data *rd)
414 struct Plugin *plugin = cls;
418 struct GNUNET_HashCode hkey;
419 struct FlatFileEntry *entry;
421 rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
423 key_len = strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
424 key = GNUNET_malloc (key_len);
428 GNUNET_memcpy (key + strlen(label),
430 sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
431 GNUNET_CRYPTO_hash (key,
434 GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm,
438 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
443 entry = GNUNET_new (struct FlatFileEntry);
444 entry->private_key = GNUNET_new (struct GNUNET_CRYPTO_EcdsaPrivateKey);
445 GNUNET_asprintf (&entry->label,
448 GNUNET_memcpy (entry->private_key,
450 sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
451 entry->rvalue = rvalue;
452 entry->record_count = rd_count;
453 entry->record_data = GNUNET_new_array (rd_count,
454 struct GNUNET_GNSRECORD_Data);
455 for (unsigned int i = 0; i < rd_count; i++)
457 entry->record_data[i].expiration_time = rd[i].expiration_time;
458 entry->record_data[i].record_type = rd[i].record_type;
459 entry->record_data[i].flags = rd[i].flags;
460 entry->record_data[i].data_size = rd[i].data_size;
461 entry->record_data[i].data = GNUNET_malloc (rd[i].data_size);
462 GNUNET_memcpy ((char*)entry->record_data[i].data,
466 return GNUNET_CONTAINER_multihashmap_put (plugin->hm,
469 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
474 * Lookup records in the datastore for which we are the authority.
476 * @param cls closure (internal context for the plugin)
477 * @param zone private key of the zone
478 * @param label name of the record in the zone
479 * @param iter function to call with the result
480 * @param iter_cls closure for @a iter
481 * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
484 namestore_flat_lookup_records (void *cls,
485 const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
487 GNUNET_NAMESTORE_RecordIterator iter,
490 struct Plugin *plugin = cls;
491 struct FlatFileEntry *entry;
492 struct GNUNET_HashCode hkey;
499 return GNUNET_SYSERR;
501 key_len = strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
502 key = GNUNET_malloc (key_len);
506 GNUNET_memcpy (key+strlen(label),
508 sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
509 GNUNET_CRYPTO_hash (key,
514 entry = GNUNET_CONTAINER_multihashmap_get (plugin->hm,
531 * Closure for #iterate_zones.
533 struct IterateContext
536 * How many more records should we skip before returning results?
541 * How many more records should we return?
546 * What is the position of the current entry, counting
554 const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone;
557 * Function to call on each record.
559 GNUNET_NAMESTORE_RecordIterator iter;
562 * Closure for @e iter.
570 * Helper function for #namestore_flat_iterate_records().
572 * @param cls a `struct IterateContext`
574 * @param value a `struct FlatFileEntry`
575 * @return #GNUNET_YES to continue the iteration
578 iterate_zones (void *cls,
579 const struct GNUNET_HashCode *key,
582 struct IterateContext *ic = cls;
583 struct FlatFileEntry *entry = value;
588 if ( (NULL != ic->zone) &&
589 (0 != memcmp (entry->private_key,
591 sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey))) )
599 ic->iter (ic->iter_cls,
613 * Iterate over the results for a particular key and zone in the
614 * datastore. Will return at most one result to the iterator.
616 * @param cls closure (internal context for the plugin)
617 * @param zone hash of public key of the zone, NULL to iterate over all zones
618 * @param serial serial number to exclude in the list of all matching records
619 * @param limit maximum number of results to return to @a iter
620 * @param iter function to call with the result
621 * @param iter_cls closure for @a iter
622 * @return #GNUNET_OK on success, #GNUNET_NO if there were no more results, #GNUNET_SYSERR on error
625 namestore_flat_iterate_records (void *cls,
626 const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
629 GNUNET_NAMESTORE_RecordIterator iter,
632 struct Plugin *plugin = cls;
633 struct IterateContext ic;
639 ic.iter_cls = iter_cls;
641 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
644 return (0 == ic.limit) ? GNUNET_OK : GNUNET_NO;
649 * Closure for #zone_to_name.
651 struct ZoneToNameContext
653 const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone;
654 const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone;
655 GNUNET_NAMESTORE_RecordIterator iter;
663 zone_to_name (void *cls,
664 const struct GNUNET_HashCode *key,
667 struct ZoneToNameContext *ztn = cls;
668 struct FlatFileEntry *entry = value;
671 if (0 != memcmp (entry->private_key,
673 sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)))
676 for (unsigned int i = 0; i < entry->record_count; i++)
678 if (GNUNET_GNSRECORD_TYPE_PKEY != entry->record_data[i].record_type)
680 if (0 == memcmp (ztn->value_zone,
681 entry->record_data[i].data,
682 sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)))
684 ztn->iter (ztn->iter_cls,
690 ztn->result_found = GNUNET_YES;
698 * Look for an existing PKEY delegation record for a given public key.
699 * Returns at most one result to the iterator.
701 * @param cls closure (internal context for the plugin)
702 * @param zone private key of the zone to look up in, never NULL
703 * @param value_zone public key of the target zone (value), never NULL
704 * @param iter function to call with the result
705 * @param iter_cls closure for @a iter
706 * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
709 namestore_flat_zone_to_name (void *cls,
710 const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
711 const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone,
712 GNUNET_NAMESTORE_RecordIterator iter,
715 struct Plugin *plugin = cls;
716 struct ZoneToNameContext ztn = {
718 .iter_cls = iter_cls,
720 .value_zone = value_zone,
721 .result_found = GNUNET_NO
724 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
725 "Performing reverse lookup for `%s'\n",
726 GNUNET_GNSRECORD_z2s (value_zone));
727 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
730 return ztn.result_found;
735 * Entry point for the plugin.
737 * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
738 * @return NULL on error, otherwise the plugin context
741 libgnunet_plugin_namestore_flat_init (void *cls)
743 static struct Plugin plugin;
744 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
745 struct GNUNET_NAMESTORE_PluginFunctions *api;
747 if (NULL != plugin.cfg)
748 return NULL; /* can only initialize once! */
751 sizeof (struct Plugin));
753 if (GNUNET_OK != database_setup (&plugin))
755 database_shutdown (&plugin);
758 api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
760 api->store_records = &namestore_flat_store_records;
761 api->iterate_records = &namestore_flat_iterate_records;
762 api->zone_to_name = &namestore_flat_zone_to_name;
763 api->lookup_records = &namestore_flat_lookup_records;
764 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
765 _("flat file database running\n"));
771 * Exit point from the plugin.
773 * @param cls the plugin context (as returned by "init")
774 * @return always NULL
777 libgnunet_plugin_namestore_flat_done (void *cls)
779 struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
780 struct Plugin *plugin = api->cls;
782 database_shutdown (plugin);
785 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
786 "flat file plugin is finished\n");
790 /* end of plugin_namestore_flat.c */