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