2 * This file is part of GNUnet
3 * Copyright (C) 2009-2015, 2018, 2019 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/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
21 * @file namestore/plugin_namestore_flat.c
22 * @brief file-based namestore backend
23 * @author Martin Schanzenbach
24 * @author Christian Grothoff
28 #include "gnunet_namestore_plugin.h"
29 #include "gnunet_namestore_service.h"
30 #include "gnunet_gnsrecord_lib.h"
31 #include "namestore.h"
34 * Context for all functions in this plugin.
38 const struct GNUNET_CONFIGURATION_Handle *cfg;
48 struct GNUNET_CONTAINER_MultiHashMap *hm;
57 struct GNUNET_CRYPTO_EcdsaPrivateKey private_key;
62 uint32_t record_count;
72 struct GNUNET_GNSRECORD_Data *record_data;
82 * Hash contactenation of @a pkey and @a label into @a h
85 * @param label a label
86 * @param h[out] initialized hash
89 hash_pkey_and_label (const struct GNUNET_CRYPTO_EcdsaPrivateKey *pkey,
91 struct GNUNET_HashCode *h)
97 label_len = strlen (label);
98 key_len = label_len + sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey);
99 key = GNUNET_malloc (key_len);
103 GNUNET_memcpy (key + label_len,
105 sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey));
106 GNUNET_CRYPTO_hash (key,
114 * Initialize the database connections and associated
115 * data structures (create tables and indices
116 * as needed as well).
118 * @param plugin the plugin context (state for this module)
119 * @return #GNUNET_OK on success
122 database_setup (struct Plugin *plugin)
126 char *zone_private_key;
127 char *record_data_b64;
133 size_t record_data_size;
135 struct GNUNET_HashCode hkey;
136 struct GNUNET_DISK_FileHandle *fh;
137 struct FlatFileEntry *entry;
138 struct GNUNET_DISK_MapHandle *mh;
141 GNUNET_CONFIGURATION_get_value_filename (plugin->cfg,
146 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
149 return GNUNET_SYSERR;
152 GNUNET_DISK_file_test (flatdbfile))
155 GNUNET_DISK_directory_create_for_file (flatdbfile))
158 GNUNET_free (flatdbfile);
159 return GNUNET_SYSERR;
162 /* flatdbfile should be UTF-8-encoded. If it isn't, it's a bug */
163 plugin->fn = flatdbfile;
165 /* Load data from file into hashmap */
166 plugin->hm = GNUNET_CONTAINER_multihashmap_create (10,
168 fh = GNUNET_DISK_file_open (flatdbfile,
169 GNUNET_DISK_OPEN_CREATE
170 | GNUNET_DISK_OPEN_READWRITE,
171 GNUNET_DISK_PERM_USER_WRITE
172 | GNUNET_DISK_PERM_USER_READ);
175 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
176 _ ("Unable to initialize file: %s.\n"),
178 return GNUNET_SYSERR;
181 GNUNET_DISK_file_size (flatdbfile,
186 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
187 _ ("Unable to get filesize: %s.\n"),
189 GNUNET_DISK_file_close (fh);
190 return GNUNET_SYSERR;
194 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
195 _ ("File too big to map: %llu bytes.\n"),
196 (unsigned long long) size);
197 GNUNET_DISK_file_close (fh);
198 return GNUNET_SYSERR;
202 GNUNET_DISK_file_close (fh);
205 buffer = GNUNET_DISK_file_map (fh,
207 GNUNET_DISK_MAP_TYPE_READ,
211 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
213 GNUNET_DISK_file_close (fh);
214 return GNUNET_SYSERR;
216 if ('\0' != buffer[size - 1])
218 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
219 _ ("Namestore database file `%s' malformed\n"),
221 GNUNET_DISK_file_unmap (mh);
222 GNUNET_DISK_file_close (fh);
223 return GNUNET_SYSERR;
226 line = strtok (buffer, "\n");
229 zone_private_key = strtok (line, ",");
230 if (NULL == zone_private_key)
232 rvalue = strtok (NULL, ",");
235 record_count = strtok (NULL, ",");
236 if (NULL == record_count)
238 record_data_b64 = strtok (NULL, ",");
239 if (NULL == record_data_b64)
241 label = strtok (NULL, ",");
244 line = strtok (NULL, "\n");
245 entry = GNUNET_new (struct FlatFileEntry);
247 unsigned long long ll;
249 if (1 != sscanf (rvalue,
253 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
254 "Error parsing entry\n");
258 entry->rvalue = (uint64_t) ll;
263 if (1 != sscanf (record_count,
267 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
268 "Error parsing entry\n");
272 entry->record_count = (uint32_t) ui;
274 entry->label = GNUNET_strdup (label);
276 = GNUNET_STRINGS_base64_decode (record_data_b64,
277 strlen (record_data_b64),
278 (void **) &record_data);
280 GNUNET_new_array (entry->record_count,
281 struct GNUNET_GNSRECORD_Data);
283 GNUNET_GNSRECORD_records_deserialize (record_data_size,
288 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
289 "Unable to deserialize record %s\n",
291 GNUNET_free (entry->label);
293 GNUNET_free (record_data);
296 GNUNET_free (record_data);
299 struct GNUNET_CRYPTO_EcdsaPrivateKey *private_key;
301 GNUNET_STRINGS_base64_decode (zone_private_key,
302 strlen (zone_private_key),
303 (void **) &private_key);
304 entry->private_key = *private_key;
305 GNUNET_free (private_key);
308 hash_pkey_and_label (&entry->private_key,
312 GNUNET_CONTAINER_multihashmap_put (plugin->hm,
315 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
321 GNUNET_DISK_file_unmap (mh);
322 GNUNET_DISK_file_close (fh);
328 * Store values in hashmap in file and free data
330 * @param plugin the plugin context
331 * @param key key in the map
332 * @param value a `struct FlatFileEntry`
335 store_and_free_entries (void *cls,
336 const struct GNUNET_HashCode *key,
339 struct GNUNET_DISK_FileHandle *fh = cls;
340 struct FlatFileEntry *entry = value;
342 char *zone_private_key;
343 char *record_data_b64;
347 GNUNET_STRINGS_base64_encode (&entry->private_key,
348 sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey),
350 data_size = GNUNET_GNSRECORD_records_get_size (entry->record_count,
355 GNUNET_free (zone_private_key);
356 return GNUNET_SYSERR;
358 if (data_size >= UINT16_MAX)
361 GNUNET_free (zone_private_key);
362 return GNUNET_SYSERR;
365 char data[data_size];
368 ret = GNUNET_GNSRECORD_records_serialize (entry->record_count,
376 GNUNET_free (zone_private_key);
377 return GNUNET_SYSERR;
379 GNUNET_STRINGS_base64_encode (data,
383 GNUNET_asprintf (&line,
384 "%s,%llu,%u,%s,%s\n",
386 (unsigned long long) entry->rvalue,
387 (unsigned int) entry->record_count,
390 GNUNET_free (record_data_b64);
391 GNUNET_free (zone_private_key);
393 GNUNET_DISK_file_write (fh,
398 GNUNET_free (entry->label);
399 GNUNET_free (entry->record_data);
406 * Shutdown database connection and associate data
408 * @param plugin the plugin context (state for this module)
411 database_shutdown (struct Plugin *plugin)
413 struct GNUNET_DISK_FileHandle *fh;
415 fh = GNUNET_DISK_file_open (plugin->fn,
416 GNUNET_DISK_OPEN_CREATE
417 | GNUNET_DISK_OPEN_TRUNCATE
418 | GNUNET_DISK_OPEN_READWRITE,
419 GNUNET_DISK_PERM_USER_WRITE
420 | GNUNET_DISK_PERM_USER_READ);
423 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
424 _ ("Unable to initialize file: %s.\n"),
429 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
430 &store_and_free_entries,
432 GNUNET_CONTAINER_multihashmap_destroy (plugin->hm);
433 /* append 0-terminator */
434 GNUNET_DISK_file_write (fh,
437 GNUNET_DISK_file_close (fh);
442 * Store a record in the datastore. Removes any existing record in the
443 * same zone with the same name.
445 * @param cls closure (internal context for the plugin)
446 * @param zone_key private key of the zone
447 * @param label name that is being mapped (at most 255 characters long)
448 * @param rd_count number of entries in @a rd array
449 * @param rd array of records with data to store
450 * @return #GNUNET_OK on success, else #GNUNET_SYSERR
453 namestore_flat_store_records (void *cls,
455 GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
457 unsigned int rd_count,
458 const struct GNUNET_GNSRECORD_Data *rd)
460 struct Plugin *plugin = cls;
462 struct GNUNET_HashCode hkey;
463 struct FlatFileEntry *entry;
465 rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
467 hash_pkey_and_label (zone_key,
470 GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm,
474 GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
479 entry = GNUNET_new (struct FlatFileEntry);
480 GNUNET_asprintf (&entry->label,
483 GNUNET_memcpy (&entry->private_key,
485 sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey));
486 entry->rvalue = rvalue;
487 entry->record_count = rd_count;
488 entry->record_data = GNUNET_new_array (rd_count,
489 struct GNUNET_GNSRECORD_Data);
490 for (unsigned int i = 0; i < rd_count; i++)
492 entry->record_data[i].expiration_time = rd[i].expiration_time;
493 entry->record_data[i].record_type = rd[i].record_type;
494 entry->record_data[i].flags = rd[i].flags;
495 entry->record_data[i].data_size = rd[i].data_size;
496 entry->record_data[i].data = GNUNET_malloc (rd[i].data_size);
497 GNUNET_memcpy ((char *) entry->record_data[i].data,
501 return GNUNET_CONTAINER_multihashmap_put (plugin->hm,
504 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
509 * Lookup records in the datastore for which we are the authority.
511 * @param cls closure (internal context for the plugin)
512 * @param zone private key of the zone
513 * @param label name of the record in the zone
514 * @param iter function to call with the result
515 * @param iter_cls closure for @a iter
516 * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
519 namestore_flat_lookup_records (void *cls,
520 const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
522 GNUNET_NAMESTORE_RecordIterator iter,
525 struct Plugin *plugin = cls;
526 struct FlatFileEntry *entry;
527 struct GNUNET_HashCode hkey;
532 return GNUNET_SYSERR;
534 hash_pkey_and_label (zone,
537 entry = GNUNET_CONTAINER_multihashmap_get (plugin->hm,
544 1, /* zero is illegal */
554 * Closure for #iterate_zones.
556 struct IterateContext
559 * How many more records should we skip before returning results?
564 * How many more records should we return?
569 * What is the position of the current entry, counting
577 const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone;
580 * Function to call on each record.
582 GNUNET_NAMESTORE_RecordIterator iter;
585 * Closure for @e iter.
592 * Helper function for #namestore_flat_iterate_records().
594 * @param cls a `struct IterateContext`
596 * @param value a `struct FlatFileEntry`
597 * @return #GNUNET_YES to continue the iteration
600 iterate_zones (void *cls,
601 const struct GNUNET_HashCode *key,
604 struct IterateContext *ic = cls;
605 struct FlatFileEntry *entry = value;
610 if ((NULL != ic->zone) &&
611 (0 != GNUNET_memcmp (&entry->private_key,
620 ic->iter (ic->iter_cls,
623 ? &entry->private_key
636 * Iterate over the results for a particular key and zone in the
637 * datastore. Will return at most one result to the iterator.
639 * @param cls closure (internal context for the plugin)
640 * @param zone hash of public key of the zone, NULL to iterate over all zones
641 * @param serial serial number to exclude in the list of all matching records
642 * @param limit maximum number of results to return to @a iter
643 * @param iter function to call with the result
644 * @param iter_cls closure for @a iter
645 * @return #GNUNET_OK on success, #GNUNET_NO if there were no more results, #GNUNET_SYSERR on error
648 namestore_flat_iterate_records (void *cls,
650 GNUNET_CRYPTO_EcdsaPrivateKey *zone,
653 GNUNET_NAMESTORE_RecordIterator iter,
656 struct Plugin *plugin = cls;
657 struct IterateContext ic;
663 ic.iter_cls = iter_cls;
665 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
668 return (0 == ic.limit) ? GNUNET_OK : GNUNET_NO;
673 * Closure for #zone_to_name.
675 struct ZoneToNameContext
677 const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone;
678 const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone;
679 GNUNET_NAMESTORE_RecordIterator iter;
687 zone_to_name (void *cls,
688 const struct GNUNET_HashCode *key,
691 struct ZoneToNameContext *ztn = cls;
692 struct FlatFileEntry *entry = value;
695 if (0 != GNUNET_memcmp (&entry->private_key,
699 for (unsigned int i = 0; i < entry->record_count; i++)
701 if (GNUNET_GNSRECORD_TYPE_PKEY != entry->record_data[i].record_type)
703 if (0 == memcmp (ztn->value_zone,
704 entry->record_data[i].data,
705 sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey)))
707 ztn->iter (ztn->iter_cls,
708 i + 1, /* zero is illegal! */
713 ztn->result_found = GNUNET_YES;
721 * Look for an existing PKEY delegation record for a given public key.
722 * Returns at most one result to the iterator.
724 * @param cls closure (internal context for the plugin)
725 * @param zone private key of the zone to look up in, never NULL
726 * @param value_zone public key of the target zone (value), never NULL
727 * @param iter function to call with the result
728 * @param iter_cls closure for @a iter
729 * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
732 namestore_flat_zone_to_name (void *cls,
733 const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
735 GNUNET_CRYPTO_EcdsaPublicKey *value_zone,
736 GNUNET_NAMESTORE_RecordIterator iter,
739 struct Plugin *plugin = cls;
740 struct ZoneToNameContext ztn = {
742 .iter_cls = iter_cls,
744 .value_zone = value_zone,
745 .result_found = GNUNET_NO
748 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
749 "Performing reverse lookup for `%s'\n",
750 GNUNET_GNSRECORD_z2s (value_zone));
751 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
754 return ztn.result_found;
759 * Entry point for the plugin.
761 * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
762 * @return NULL on error, otherwise the plugin context
765 libgnunet_plugin_namestore_flat_init (void *cls)
767 static struct Plugin plugin;
768 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
769 struct GNUNET_NAMESTORE_PluginFunctions *api;
771 if (NULL != plugin.cfg)
772 return NULL; /* can only initialize once! */
775 sizeof(struct Plugin));
777 if (GNUNET_OK != database_setup (&plugin))
779 database_shutdown (&plugin);
782 api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
784 api->store_records = &namestore_flat_store_records;
785 api->iterate_records = &namestore_flat_iterate_records;
786 api->zone_to_name = &namestore_flat_zone_to_name;
787 api->lookup_records = &namestore_flat_lookup_records;
788 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
789 _ ("Flat file database running\n"));
795 * Exit point from the plugin.
797 * @param cls the plugin context (as returned by "init")
798 * @return always NULL
801 libgnunet_plugin_namestore_flat_done (void *cls)
803 struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
804 struct Plugin *plugin = api->cls;
806 database_shutdown (plugin);
809 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
810 "Flat file plugin is finished\n");
815 /* end of plugin_namestore_flat.c */