/* * This file is part of GNUnet * Copyright (C) 2009-2015, 2018, 2019 GNUnet e.V. * * GNUnet is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * GNUnet is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @file namestore/plugin_namestore_flat.c * @brief file-based namestore backend * @author Martin Schanzenbach * @author Christian Grothoff */ #include "platform.h" #include "gnunet_namestore_plugin.h" #include "gnunet_namestore_service.h" #include "gnunet_gnsrecord_lib.h" #include "namestore.h" /** * Context for all functions in this plugin. */ struct Plugin { const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Database filename. */ char *fn; /** * HashMap */ struct GNUNET_CONTAINER_MultiHashMap *hm; }; struct FlatFileEntry { /** * Entry zone */ struct GNUNET_IDENTITY_PrivateKey private_key; /** * Record cound */ uint32_t record_count; /** * Rvalue */ uint64_t rvalue; /** * Record data */ struct GNUNET_GNSRECORD_Data *record_data; /** * Label */ char *label; }; /** * Hash contactenation of @a pkey and @a label into @a h * * @param pkey a key * @param label a label * @param h[out] initialized hash */ static void hash_pkey_and_label (const struct GNUNET_IDENTITY_PrivateKey *pkey, const char *label, struct GNUNET_HashCode *h) { char *key; size_t label_len; size_t key_len; label_len = strlen (label); key_len = label_len + sizeof(struct GNUNET_IDENTITY_PrivateKey); key = GNUNET_malloc (key_len); GNUNET_memcpy (key, label, label_len); GNUNET_memcpy (key + label_len, pkey, sizeof(struct GNUNET_IDENTITY_PrivateKey)); GNUNET_CRYPTO_hash (key, key_len, h); GNUNET_free (key); } /** * Initialize the database connections and associated * data structures (create tables and indices * as needed as well). * * @param plugin the plugin context (state for this module) * @return #GNUNET_OK on success */ static int database_setup (struct Plugin *plugin) { char *flatdbfile; char *record_data; char *zone_private_key; char *record_data_b64; char *buffer; char *line; char *label; char *rvalue; char *record_count; size_t record_data_size; uint64_t size; struct GNUNET_HashCode hkey; struct GNUNET_DISK_FileHandle *fh; struct FlatFileEntry *entry; struct GNUNET_DISK_MapHandle *mh; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (plugin->cfg, "namestore-flat", "FILENAME", &flatdbfile)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "namestore-flat", "FILENAME"); return GNUNET_SYSERR; } if (GNUNET_OK != GNUNET_DISK_file_test (flatdbfile)) { if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (flatdbfile)) { GNUNET_break (0); GNUNET_free (flatdbfile); return GNUNET_SYSERR; } } /* flatdbfile should be UTF-8-encoded. If it isn't, it's a bug */ plugin->fn = flatdbfile; /* Load data from file into hashmap */ plugin->hm = GNUNET_CONTAINER_multihashmap_create (10, GNUNET_NO); fh = GNUNET_DISK_file_open (flatdbfile, GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_READWRITE, GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_USER_READ); if (NULL == fh) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Unable to initialize file: %s.\n"), flatdbfile); return GNUNET_SYSERR; } if (GNUNET_SYSERR == GNUNET_DISK_file_size (flatdbfile, &size, GNUNET_YES, GNUNET_YES)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Unable to get filesize: %s.\n"), flatdbfile); GNUNET_DISK_file_close (fh); return GNUNET_SYSERR; } if (size > SIZE_MAX) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("File too big to map: %llu bytes.\n"), (unsigned long long) size); GNUNET_DISK_file_close (fh); return GNUNET_SYSERR; } if (0 == size) { GNUNET_DISK_file_close (fh); return GNUNET_OK; } buffer = GNUNET_DISK_file_map (fh, &mh, GNUNET_DISK_MAP_TYPE_READ, size); if (NULL == buffer) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "mmap"); GNUNET_DISK_file_close (fh); return GNUNET_SYSERR; } if ('\0' != buffer[size - 1]) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Namestore database file `%s' malformed\n"), flatdbfile); GNUNET_DISK_file_unmap (mh); GNUNET_DISK_file_close (fh); return GNUNET_SYSERR; } line = strtok (buffer, "\n"); while (NULL != line) { zone_private_key = strtok (line, ","); if (NULL == zone_private_key) break; rvalue = strtok (NULL, ","); if (NULL == rvalue) break; record_count = strtok (NULL, ","); if (NULL == record_count) break; record_data_b64 = strtok (NULL, ","); if (NULL == record_data_b64) break; label = strtok (NULL, ","); if (NULL == label) break; line = strtok (NULL, "\n"); entry = GNUNET_new (struct FlatFileEntry); { unsigned long long ll; if (1 != sscanf (rvalue, "%llu", &ll)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error parsing entry\n"); GNUNET_free (entry); break; } entry->rvalue = (uint64_t) ll; } { unsigned int ui; if (1 != sscanf (record_count, "%u", &ui)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error parsing entry\n"); GNUNET_free (entry); break; } entry->record_count = (uint32_t) ui; } entry->label = GNUNET_strdup (label); record_data_size = GNUNET_STRINGS_base64_decode (record_data_b64, strlen (record_data_b64), (void **) &record_data); entry->record_data = GNUNET_new_array (entry->record_count, struct GNUNET_GNSRECORD_Data); if (GNUNET_OK != GNUNET_GNSRECORD_records_deserialize (record_data_size, record_data, entry->record_count, entry->record_data)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unable to deserialize record %s\n", label); GNUNET_free (entry->label); GNUNET_free (entry); GNUNET_free (record_data); break; } GNUNET_free (record_data); { struct GNUNET_IDENTITY_PrivateKey *private_key; GNUNET_STRINGS_base64_decode (zone_private_key, strlen (zone_private_key), (void **) &private_key); entry->private_key = *private_key; GNUNET_free (private_key); } hash_pkey_and_label (&entry->private_key, label, &hkey); if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put (plugin->hm, &hkey, entry, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) { GNUNET_free (entry); GNUNET_break (0); } } GNUNET_DISK_file_unmap (mh); GNUNET_DISK_file_close (fh); return GNUNET_OK; } /** * Store values in hashmap in file and free data * * @param plugin the plugin context * @param key key in the map * @param value a `struct FlatFileEntry` */ static int store_and_free_entries (void *cls, const struct GNUNET_HashCode *key, void *value) { struct GNUNET_DISK_FileHandle *fh = cls; struct FlatFileEntry *entry = value; char *line; char *zone_private_key; char *record_data_b64; ssize_t data_size; (void) key; GNUNET_STRINGS_base64_encode (&entry->private_key, sizeof(struct GNUNET_IDENTITY_PrivateKey), &zone_private_key); data_size = GNUNET_GNSRECORD_records_get_size (entry->record_count, entry->record_data); if (data_size < 0) { GNUNET_break (0); GNUNET_free (zone_private_key); return GNUNET_SYSERR; } if (data_size >= UINT16_MAX) { GNUNET_break (0); GNUNET_free (zone_private_key); return GNUNET_SYSERR; } { char data[data_size]; ssize_t ret; ret = GNUNET_GNSRECORD_records_serialize (entry->record_count, entry->record_data, data_size, data); if ((ret < 0) || (data_size != ret)) { GNUNET_break (0); GNUNET_free (zone_private_key); return GNUNET_SYSERR; } GNUNET_STRINGS_base64_encode (data, data_size, &record_data_b64); } GNUNET_asprintf (&line, "%s,%llu,%u,%s,%s\n", zone_private_key, (unsigned long long) entry->rvalue, (unsigned int) entry->record_count, record_data_b64, entry->label); GNUNET_free (record_data_b64); GNUNET_free (zone_private_key); GNUNET_DISK_file_write (fh, line, strlen (line)); GNUNET_free (line); GNUNET_free (entry->label); GNUNET_free (entry->record_data); GNUNET_free (entry); return GNUNET_YES; } /** * Shutdown database connection and associate data * structures. * @param plugin the plugin context (state for this module) */ static void database_shutdown (struct Plugin *plugin) { struct GNUNET_DISK_FileHandle *fh; fh = GNUNET_DISK_file_open (plugin->fn, GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_TRUNCATE | GNUNET_DISK_OPEN_READWRITE, GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_USER_READ); if (NULL == fh) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Unable to initialize file: %s.\n"), plugin->fn); return; } GNUNET_CONTAINER_multihashmap_iterate (plugin->hm, &store_and_free_entries, fh); GNUNET_CONTAINER_multihashmap_destroy (plugin->hm); /* append 0-terminator */ GNUNET_DISK_file_write (fh, "", 1); GNUNET_DISK_file_close (fh); } /** * Store a record in the datastore. Removes any existing record in the * same zone with the same name. * * @param cls closure (internal context for the plugin) * @param zone_key private key of the zone * @param label name that is being mapped (at most 255 characters long) * @param rd_count number of entries in @a rd array * @param rd array of records with data to store * @return #GNUNET_OK on success, else #GNUNET_SYSERR */ static int namestore_flat_store_records (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone_key, const char *label, unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd) { struct Plugin *plugin = cls; uint64_t rvalue; struct GNUNET_HashCode hkey; struct FlatFileEntry *entry; rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX); hash_pkey_and_label (zone_key, label, &hkey); GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm, &hkey); if (0 == rd_count) { GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, "sqlite", "Record deleted\n"); return GNUNET_OK; } entry = GNUNET_new (struct FlatFileEntry); GNUNET_asprintf (&entry->label, label, strlen (label)); GNUNET_memcpy (&entry->private_key, zone_key, sizeof(struct GNUNET_IDENTITY_PrivateKey)); entry->rvalue = rvalue; entry->record_count = rd_count; entry->record_data = GNUNET_new_array (rd_count, struct GNUNET_GNSRECORD_Data); for (unsigned int i = 0; i < rd_count; i++) { entry->record_data[i].expiration_time = rd[i].expiration_time; entry->record_data[i].record_type = rd[i].record_type; entry->record_data[i].flags = rd[i].flags; entry->record_data[i].data_size = rd[i].data_size; entry->record_data[i].data = GNUNET_malloc (rd[i].data_size); GNUNET_memcpy ((char *) entry->record_data[i].data, rd[i].data, rd[i].data_size); } return GNUNET_CONTAINER_multihashmap_put (plugin->hm, &hkey, entry, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); } /** * Lookup records in the datastore for which we are the authority. * * @param cls closure (internal context for the plugin) * @param zone private key of the zone * @param label name of the record in the zone * @param iter function to call with the result * @param iter_cls closure for @a iter * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR */ static int namestore_flat_lookup_records (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone, const char *label, GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls) { struct Plugin *plugin = cls; struct FlatFileEntry *entry; struct GNUNET_HashCode hkey; if (NULL == zone) { GNUNET_break (0); return GNUNET_SYSERR; } hash_pkey_and_label (zone, label, &hkey); entry = GNUNET_CONTAINER_multihashmap_get (plugin->hm, &hkey); if (NULL == entry) return GNUNET_NO; if (NULL != iter) iter (iter_cls, 1, /* zero is illegal */ &entry->private_key, entry->label, entry->record_count, entry->record_data); return GNUNET_YES; } /** * Closure for #iterate_zones. */ struct IterateContext { /** * How many more records should we skip before returning results? */ uint64_t offset; /** * How many more records should we return? */ uint64_t limit; /** * What is the position of the current entry, counting * starts from 1. */ uint64_t pos; /** * Target zone. */ const struct GNUNET_IDENTITY_PrivateKey *zone; /** * Function to call on each record. */ GNUNET_NAMESTORE_RecordIterator iter; /** * Closure for @e iter. */ void *iter_cls; }; /** * Helper function for #namestore_flat_iterate_records(). * * @param cls a `struct IterateContext` * @param key unused * @param value a `struct FlatFileEntry` * @return #GNUNET_YES to continue the iteration */ static int iterate_zones (void *cls, const struct GNUNET_HashCode *key, void *value) { struct IterateContext *ic = cls; struct FlatFileEntry *entry = value; (void) key; if (0 == ic->limit) return GNUNET_NO; if ((NULL != ic->zone) && (0 != GNUNET_memcmp (&entry->private_key, ic->zone))) return GNUNET_YES; ic->pos++; if (ic->offset > 0) { ic->offset--; return GNUNET_YES; } ic->iter (ic->iter_cls, ic->pos, (NULL == ic->zone) ? &entry->private_key : ic->zone, entry->label, entry->record_count, entry->record_data); ic->limit--; if (0 == ic->limit) return GNUNET_NO; return GNUNET_YES; } /** * Iterate over the results for a particular key and zone in the * datastore. Will return at most one result to the iterator. * * @param cls closure (internal context for the plugin) * @param zone hash of public key of the zone, NULL to iterate over all zones * @param serial serial number to exclude in the list of all matching records * @param limit maximum number of results to return to @a iter * @param iter function to call with the result * @param iter_cls closure for @a iter * @return #GNUNET_OK on success, #GNUNET_NO if there were no more results, #GNUNET_SYSERR on error */ static int namestore_flat_iterate_records (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone, uint64_t serial, uint64_t limit, GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls) { struct Plugin *plugin = cls; struct IterateContext ic; ic.offset = serial; ic.pos = 0; ic.limit = limit; ic.iter = iter; ic.iter_cls = iter_cls; ic.zone = zone; GNUNET_CONTAINER_multihashmap_iterate (plugin->hm, &iterate_zones, &ic); return (0 == ic.limit) ? GNUNET_OK : GNUNET_NO; } /** * Closure for #zone_to_name. */ struct ZoneToNameContext { const struct GNUNET_IDENTITY_PrivateKey *zone; const struct GNUNET_IDENTITY_PublicKey *value_zone; GNUNET_NAMESTORE_RecordIterator iter; void *iter_cls; int result_found; }; static int zone_to_name (void *cls, const struct GNUNET_HashCode *key, void *value) { struct ZoneToNameContext *ztn = cls; struct FlatFileEntry *entry = value; (void) key; if (0 != GNUNET_memcmp (&entry->private_key, ztn->zone)) return GNUNET_YES; for (unsigned int i = 0; i < entry->record_count; i++) { if (GNUNET_NO == GNUNET_GNSRECORD_is_zonekey_type (entry->record_data[i].record_type)) continue; if (ztn->value_zone->type != entry->record_data[i].record_type) continue; if (0 == memcmp (ztn->value_zone, entry->record_data[i].data, entry->record_data[i].data_size)) { ztn->iter (ztn->iter_cls, i + 1, /* zero is illegal! */ &entry->private_key, entry->label, entry->record_count, entry->record_data); ztn->result_found = GNUNET_YES; } } return GNUNET_YES; } /** * Look for an existing PKEY delegation record for a given public key. * Returns at most one result to the iterator. * * @param cls closure (internal context for the plugin) * @param zone private key of the zone to look up in, never NULL * @param value_zone public key of the target zone (value), never NULL * @param iter function to call with the result * @param iter_cls closure for @a iter * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error */ static int namestore_flat_zone_to_name (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone, const struct GNUNET_IDENTITY_PublicKey *value_zone, GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls) { struct Plugin *plugin = cls; struct ZoneToNameContext ztn = { .iter = iter, .iter_cls = iter_cls, .zone = zone, .value_zone = value_zone, .result_found = GNUNET_NO }; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Performing reverse lookup for `%s'\n", GNUNET_GNSRECORD_z2s (value_zone)); GNUNET_CONTAINER_multihashmap_iterate (plugin->hm, &zone_to_name, &ztn); return ztn.result_found; } /** * Entry point for the plugin. * * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*" * @return NULL on error, otherwise the plugin context */ void * libgnunet_plugin_namestore_flat_init (void *cls) { static struct Plugin plugin; const struct GNUNET_CONFIGURATION_Handle *cfg = cls; struct GNUNET_NAMESTORE_PluginFunctions *api; if (NULL != plugin.cfg) return NULL; /* can only initialize once! */ memset (&plugin, 0, sizeof(struct Plugin)); plugin.cfg = cfg; if (GNUNET_OK != database_setup (&plugin)) { database_shutdown (&plugin); return NULL; } api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions); api->cls = &plugin; api->store_records = &namestore_flat_store_records; api->iterate_records = &namestore_flat_iterate_records; api->zone_to_name = &namestore_flat_zone_to_name; api->lookup_records = &namestore_flat_lookup_records; GNUNET_log (GNUNET_ERROR_TYPE_INFO, _ ("Flat file database running\n")); return api; } /** * Exit point from the plugin. * * @param cls the plugin context (as returned by "init") * @return always NULL */ void * libgnunet_plugin_namestore_flat_done (void *cls) { struct GNUNET_NAMESTORE_PluginFunctions *api = cls; struct Plugin *plugin = api->cls; database_shutdown (plugin); plugin->cfg = NULL; GNUNET_free (api); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Flat file plugin is finished\n"); return NULL; } /* end of plugin_namestore_flat.c */