718a9b29bb74e06f3cbca6169a84df5e6a942c35
[oweals/gnunet.git] / src / gns / plugin_rest_gns.c
1 /*
2    This file is part of GNUnet.
3    Copyright (C) 2012-2015 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  * @author Martin Schanzenbach
22  * @file gns/plugin_rest_gns.c
23  * @brief GNUnet GNS REST plugin
24  *
25  */
26
27 #include "platform.h"
28 #include "gnunet_rest_plugin.h"
29 #include <gnunet_dnsparser_lib.h>
30 #include <gnunet_identity_service.h>
31 #include <gnunet_gnsrecord_lib.h>
32 #include <gnunet_namestore_service.h>
33 #include <gnunet_gns_service.h>
34 #include <gnunet_rest_lib.h>
35 #include <gnunet_jsonapi_lib.h>
36 #include <jansson.h>
37
38 #define GNUNET_REST_API_NS_GNS "/gns"
39
40 #define GNUNET_REST_JSONAPI_GNS_RECORD_TYPE "record_type"
41
42 #define GNUNET_REST_JSONAPI_GNS_TYPEINFO "gns_name"
43
44 #define GNUNET_REST_JSONAPI_GNS_RECORD "records"
45
46 #define GNUNET_REST_JSONAPI_GNS_EGO "ego"
47
48 #define GNUNET_REST_JSONAPI_GNS_PKEY "pkey"
49
50 #define GNUNET_REST_JSONAPI_GNS_OPTIONS "options"
51
52 /**
53  * @brief struct returned by the initialization function of the plugin
54  */
55 struct Plugin
56 {
57   const struct GNUNET_CONFIGURATION_Handle *cfg;
58 };
59
60 const struct GNUNET_CONFIGURATION_Handle *cfg;
61
62 struct LookupHandle
63 {
64   /**
65    * Handle to GNS service.
66    */
67   struct GNUNET_GNS_Handle *gns;
68
69   /**
70    * Desired timeout for the lookup (default is no timeout).
71    */
72   struct GNUNET_TIME_Relative timeout;
73
74   /**
75    * Handle to lookup request
76    */
77   struct GNUNET_GNS_LookupRequest *lookup_request;
78
79   /**
80    * Handle to rest request
81    */
82   struct GNUNET_REST_RequestHandle *rest_handle;
83
84   /**
85    * Lookup an ego with the identity service.
86    */
87   struct GNUNET_IDENTITY_EgoLookup *el;
88
89   /**
90    * Handle for identity service.
91    */
92   struct GNUNET_IDENTITY_Handle *identity;
93
94   /**
95    * Active operation on identity service.
96    */
97   struct GNUNET_IDENTITY_Operation *id_op;
98
99   /**
100    * ID of a task associated with the resolution process.
101    */
102   struct GNUNET_SCHEDULER_Task * timeout_task;
103
104   /**
105    * The root of the received JSON or NULL
106    */
107   json_t *json_root;
108
109   /**
110    * The plugin result processor
111    */
112   GNUNET_REST_ResultProcessor proc;
113
114   /**
115    * The closure of the result processor
116    */
117   void *proc_cls;
118
119   /**
120    * The name to look up
121    */
122   char *name;
123
124   /**
125    * The ego to use
126    * In string representation from JSON
127    */
128   const char *ego_str;
129
130   /**
131    * The Pkey to use
132    * In string representation from JSON
133    */
134   const char *pkey_str;
135
136   /**
137    * The record type
138    */
139   int type;
140
141   /**
142    * The public key of to use for lookup
143    */
144   struct GNUNET_CRYPTO_EcdsaPublicKey pkey;
145
146   /**
147    * The public key to use for lookup
148    */
149   struct GNUNET_CRYPTO_EcdsaPublicKey pkeym;
150
151   /**
152    * The resolver options
153    */
154   enum GNUNET_GNS_LocalOptions options;
155
156   /**
157    * the shorten key
158    */
159   struct GNUNET_CRYPTO_EcdsaPrivateKey shorten_key;
160
161   /**
162    * HTTP response code
163    */
164   int response_code;
165
166 };
167
168
169 /**
170  * Cleanup lookup handle.
171  *
172  * @param handle Handle to clean up
173  */
174 static void
175 cleanup_handle (struct LookupHandle *handle)
176 {
177   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
178               "Cleaning up\n");
179   if (NULL != handle->json_root)
180     json_decref (handle->json_root);
181
182   if (NULL != handle->name)
183     GNUNET_free (handle->name);
184   if (NULL != handle->el)
185   {
186     GNUNET_IDENTITY_ego_lookup_cancel (handle->el);
187     handle->el = NULL;
188   }
189   if (NULL != handle->id_op)
190   {
191     GNUNET_IDENTITY_cancel (handle->id_op);
192     handle->id_op = NULL;
193   }
194   if (NULL != handle->lookup_request)
195   {
196     GNUNET_GNS_lookup_cancel (handle->lookup_request);
197     handle->lookup_request = NULL;
198   }
199   if (NULL != handle->identity)
200   {
201     GNUNET_IDENTITY_disconnect (handle->identity);
202     handle->identity = NULL;
203   }
204   if (NULL != handle->gns)
205   {
206     GNUNET_GNS_disconnect (handle->gns);
207     handle->gns = NULL;
208   }
209
210   if (NULL != handle->timeout_task)
211   {
212     GNUNET_SCHEDULER_cancel (handle->timeout_task);
213   }
214   GNUNET_free (handle);
215 }
216
217
218 /**
219  * Task run on shutdown.  Cleans up everything.
220  *
221  * @param cls unused
222  * @param tc scheduler context
223  */
224 static void
225 do_error (void *cls)
226 {
227   struct LookupHandle *handle = cls;
228   struct MHD_Response *resp;
229
230   resp = GNUNET_REST_create_json_response (NULL);
231   handle->proc (handle->proc_cls, resp, handle->response_code);
232   cleanup_handle (handle);
233 }
234
235
236 /**
237  * Create json representation of a GNSRECORD
238  *
239  * @param rd the GNSRECORD_Data
240  */
241 static json_t *
242 gnsrecord_to_json (const struct GNUNET_GNSRECORD_Data *rd)
243 {
244   const char *typename;
245   char *string_val;
246   const char *exp_str;
247   json_t *record_obj;
248
249   typename = GNUNET_GNSRECORD_number_to_typename (rd->record_type);
250   string_val = GNUNET_GNSRECORD_value_to_string (rd->record_type,
251                                                  rd->data,
252                                                  rd->data_size);
253
254   if (NULL == string_val)
255   {
256     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
257                 "Record of type %d malformed, skipping\n",
258                 (int) rd->record_type);
259     return NULL;
260   }
261   record_obj = json_object();
262   json_object_set_new (record_obj, "type", json_string (typename));
263   json_object_set_new (record_obj, "value", json_string (string_val));
264   GNUNET_free (string_val);
265
266   if (GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION & rd->flags)
267   {
268     struct GNUNET_TIME_Relative time_rel;
269     time_rel.rel_value_us = rd->expiration_time;
270     exp_str = GNUNET_STRINGS_relative_time_to_string (time_rel, 1);
271   }
272   else
273   {
274     struct GNUNET_TIME_Absolute time_abs;
275     time_abs.abs_value_us = rd->expiration_time;
276     exp_str = GNUNET_STRINGS_absolute_time_to_string (time_abs);
277   }
278   json_object_set_new (record_obj, "expiration_time", json_string (exp_str));
279
280   json_object_set_new (record_obj, "expired",
281                        json_boolean (GNUNET_YES == GNUNET_GNSRECORD_is_expired (rd)));
282   return record_obj;
283 }
284
285 /**
286  * Function called with the result of a GNS lookup.
287  *
288  * @param cls the 'const char *' name that was resolved
289  * @param rd_count number of records returned
290  * @param rd array of @a rd_count records with the results
291  */
292 static void
293 process_lookup_result (void *cls, uint32_t rd_count,
294                        const struct GNUNET_GNSRECORD_Data *rd)
295 {
296   struct LookupHandle *handle = cls;
297   struct MHD_Response *resp;
298   struct GNUNET_JSONAPI_Object *json_object;
299   struct GNUNET_JSONAPI_Resource *json_resource;
300   uint32_t i;
301   char *result;
302   json_t *result_array;
303   json_t *record_obj;
304
305   result_array = json_array();
306   json_object = GNUNET_JSONAPI_object_new ();
307   json_resource = GNUNET_JSONAPI_resource_new (GNUNET_REST_JSONAPI_GNS_TYPEINFO, handle->name);
308   handle->lookup_request = NULL;
309   for (i=0; i<rd_count; i++)
310   {
311     if ( (rd[i].record_type != handle->type) &&
312          (GNUNET_GNSRECORD_TYPE_ANY != handle->type) )
313       continue;
314     record_obj = gnsrecord_to_json (&(rd[i]));
315     json_array_append (result_array, record_obj);
316     json_decref (record_obj);
317   }
318   GNUNET_JSONAPI_resource_add_attr (json_resource,
319                                          GNUNET_REST_JSONAPI_GNS_RECORD,
320                                          result_array);
321   GNUNET_JSONAPI_object_resource_add (json_object, json_resource);
322   GNUNET_JSONAPI_data_serialize (json_object, &result);
323   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Result %s\n", result);
324   json_decref (result_array);
325   GNUNET_JSONAPI_object_delete (json_object);
326   resp = GNUNET_REST_create_json_response (result);
327   handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
328   GNUNET_free (result);
329   cleanup_handle (handle);
330 }
331
332
333 /**
334  * Perform the actual resolution, starting with the zone
335  * identified by the given public key and the shorten zone.
336  *
337  * @param pkey public key to use for the zone, can be NULL
338  * @param shorten_key private key used for shortening, can be NULL
339  */
340 static void
341 lookup_with_keys (struct LookupHandle *handle, const struct GNUNET_CRYPTO_EcdsaPrivateKey *shorten_key)
342 {
343   if (UINT32_MAX == handle->type)
344   {
345     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
346                 _("Invalid typename specified, assuming `ANY'\n"));
347     handle->type = GNUNET_GNSRECORD_TYPE_ANY;
348   }
349   if (NULL != handle->name)
350   {
351     handle->lookup_request = GNUNET_GNS_lookup (handle->gns,
352                                                 handle->name,
353                                                 &handle->pkey,
354                                                 handle->type,
355                                                 handle->options,
356                                                 shorten_key,
357                                                 &process_lookup_result,
358                                                 handle);
359   }
360   else
361   {
362     GNUNET_SCHEDULER_add_now (&do_error, handle);
363     return;
364   }
365 }
366
367 /**
368  * Method called to with the ego we are to use for shortening
369  * during the lookup.
370  *
371  * @param cls closure contains the public key to use
372  * @param ego ego handle, NULL if not found
373  * @param ctx context for application to store data for this ego
374  *                 (during the lifetime of this process, initially NULL)
375  * @param name name assigned by the user for this ego,
376  *                   NULL if the user just deleted the ego and it
377  *                   must thus no longer be used
378  */
379 static void
380 identity_shorten_cb (void *cls,
381                      struct GNUNET_IDENTITY_Ego *ego,
382                      void **ctx,
383                      const char *name)
384 {
385   struct LookupHandle *handle = cls;
386
387   handle->id_op = NULL;
388   if (NULL == ego)
389     lookup_with_keys (handle, NULL);
390   else
391     lookup_with_keys (handle,
392                       GNUNET_IDENTITY_ego_get_private_key (ego));
393 }
394
395 /**
396  * Perform the actual resolution, starting with the zone
397  * identified by the given public key.
398  *
399  * @param pkey public key to use for the zone
400  */
401 static void
402 lookup_with_public_key (struct LookupHandle *handle)
403 {
404   handle->pkeym = handle->pkey;
405   GNUNET_break (NULL == handle->id_op);
406   handle->id_op = GNUNET_IDENTITY_get (handle->identity,
407                                        "gns-short",
408                                        &identity_shorten_cb,
409                                        handle);
410   if (NULL == handle->id_op)
411   {
412     GNUNET_break (0);
413     lookup_with_keys (handle, NULL);
414   }
415 }
416
417 /**
418  * Method called to with the ego we are to use for the lookup,
419  * when the ego is determined by a name.
420  *
421  * @param cls closure (NULL, unused)
422  * @param ego ego handle, NULL if not found
423  */
424 static void
425 identity_zone_cb (void *cls,
426                   const struct GNUNET_IDENTITY_Ego *ego)
427 {
428   struct LookupHandle *handle = cls;
429
430   handle->el = NULL;
431   if (NULL == ego)
432   {
433     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
434                 _("Ego for not found, cannot perform lookup.\n"));
435     GNUNET_SCHEDULER_add_now (&do_error, handle);
436     return;
437   }
438   else
439   {
440     GNUNET_IDENTITY_ego_get_public_key (ego, &handle->pkey);
441     lookup_with_public_key (handle);
442   }
443   json_decref(handle->json_root);
444 }
445
446 /**
447  * Method called to with the ego we are to use for the lookup,
448  * when the ego is the one for the default master zone.
449  *
450  * @param cls closure (NULL, unused)
451  * @param ego ego handle, NULL if not found
452  * @param ctx context for application to store data for this ego
453  *                 (during the lifetime of this process, initially NULL)
454  * @param name name assigned by the user for this ego,
455  *                   NULL if the user just deleted the ego and it
456  *                   must thus no longer be used
457  */
458 static void
459 identity_master_cb (void *cls,
460                     struct GNUNET_IDENTITY_Ego *ego,
461                     void **ctx,
462                     const char *name)
463 {
464   const char *dot;
465   struct LookupHandle *handle = cls;
466
467   handle->id_op = NULL;
468   if (NULL == ego)
469   {
470     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
471                 _("Ego for `gns-master' not found, cannot perform lookup.  Did you run gnunet-gns-import.sh?\n"));
472     GNUNET_SCHEDULER_add_now (&do_error, handle);
473     return;
474   }
475   GNUNET_IDENTITY_ego_get_public_key (ego, &handle->pkey);
476   /* main name is our own master zone, do no look for that in the DHT */
477   handle->options = GNUNET_GNS_LO_LOCAL_MASTER;
478   /* if the name is of the form 'label.gnu', never go to the DHT */
479   dot = NULL;
480   if (NULL != handle->name)
481     dot = strchr (handle->name, '.');
482   if ( (NULL != dot) &&
483        (0 == strcasecmp (dot, ".gnu")) )
484     handle->options = GNUNET_GNS_LO_NO_DHT;
485   lookup_with_public_key (handle);
486 }
487
488 /**
489  * Parse REST uri for name and record type
490  *
491  * @param url Url to parse
492  * @param handle lookup handle to populate
493  * @return GNUNET_SYSERR on error
494  */
495 static int
496 parse_url (const char *url, struct LookupHandle *handle)
497 {
498   char *name;
499   char tmp_url[strlen(url)+1];
500   char *tok;
501
502   strcpy (tmp_url, url);
503   tok = strtok ((char*)tmp_url, "/");
504   if (NULL == tok)
505     return GNUNET_SYSERR;
506   name = strtok (NULL, "/");
507   if (NULL == name)
508     return GNUNET_SYSERR;
509   GNUNET_asprintf (&handle->name,
510                    "%s",
511                    name);
512   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
513               "Got name: %s\n", handle->name);
514   return GNUNET_OK;
515 }
516
517 static void
518 get_gns_cont (struct GNUNET_REST_RequestHandle *conndata_handle,
519               const char* url,
520               void *cls)
521 {
522   struct LookupHandle *handle = cls;
523   struct GNUNET_HashCode key;
524
525   //parse name and type from url
526   if (GNUNET_OK != parse_url (url, handle))
527   {
528     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error parsing url...\n");
529     GNUNET_SCHEDULER_add_now (&do_error, handle);
530     return;
531   }
532   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
533               "Connecting...\n");
534   handle->gns = GNUNET_GNS_connect (cfg);
535   handle->identity = GNUNET_IDENTITY_connect (cfg, NULL, NULL);
536   handle->timeout_task = GNUNET_SCHEDULER_add_delayed (handle->timeout,
537                                                        &do_error, handle);
538   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
539               "Connected\n");
540   if (NULL == handle->gns)
541   {
542     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
543                 "Connecting to GNS failed\n");
544     GNUNET_SCHEDULER_add_now (&do_error, handle);
545     return;
546   }
547   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_OPTIONS,
548                       strlen (GNUNET_REST_JSONAPI_GNS_OPTIONS),
549                       &key);
550   handle->options = GNUNET_GNS_LO_DEFAULT;
551   if ( GNUNET_YES ==
552        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
553                                                &key) )
554   {
555     handle->options = GNUNET_GNS_LO_DEFAULT;//TODO(char*) GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
556     //&key);
557   }
558   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_RECORD_TYPE,
559                       strlen (GNUNET_REST_JSONAPI_GNS_RECORD_TYPE),
560                       &key);
561   if ( GNUNET_YES ==
562        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
563                                                &key) )
564   {
565     handle->type = GNUNET_GNSRECORD_typename_to_number
566       (GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
567                                           &key));
568   }
569   else
570     handle->type = GNUNET_GNSRECORD_TYPE_ANY;
571
572   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_PKEY,
573                       strlen (GNUNET_REST_JSONAPI_GNS_PKEY),
574                       &key);
575   if ( GNUNET_YES ==
576        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
577                                                &key) )
578   {
579     handle->pkey_str = GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
580                                                           &key);
581     if (GNUNET_OK !=
582         GNUNET_CRYPTO_ecdsa_public_key_from_string (handle->pkey_str,
583                                                     strlen(handle->pkey_str),
584                                                     &(handle->pkey)))
585     {
586       GNUNET_SCHEDULER_add_now (&do_error, handle);
587       return;
588     }
589     lookup_with_public_key (handle);
590     return;
591   }
592   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_EGO,
593                       strlen (GNUNET_REST_JSONAPI_GNS_EGO),
594                       &key);
595   if ( GNUNET_YES ==
596        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
597                                                &key) )
598   {
599     handle->ego_str = GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
600                                                          &key);
601     handle->el = GNUNET_IDENTITY_ego_lookup (cfg,
602                                              handle->ego_str,
603                                              &identity_zone_cb,
604                                              handle);
605     return;
606   }
607   if ( (NULL != handle->name) &&
608        (strlen (handle->name) > 4) &&
609        (0 == strcmp (".zkey",
610                      &handle->name[strlen (handle->name) - 4])) )
611   {
612     GNUNET_CRYPTO_ecdsa_key_get_public
613       (GNUNET_CRYPTO_ecdsa_key_get_anonymous (),
614        &(handle->pkey));
615     lookup_with_public_key (handle);
616   }
617   else
618   {
619     GNUNET_break (NULL == handle->id_op);
620     handle->id_op = GNUNET_IDENTITY_get (handle->identity,
621                                          "gns-master",
622                                          &identity_master_cb,
623                                          handle);
624     GNUNET_assert (NULL != handle->id_op);
625   }
626 }
627
628 /**
629  * Handle rest request
630  *
631  * @param handle the lookup handle
632  */
633 static void
634 options_cont (struct GNUNET_REST_RequestHandle *con_handle,
635               const char* url,
636               void *cls)
637 {
638   struct MHD_Response *resp;
639   struct LookupHandle *handle = cls;
640
641   //For GNS, independent of path return all options
642   resp = GNUNET_REST_create_json_response (NULL);
643   MHD_add_response_header (resp,
644                            "Access-Control-Allow-Methods",
645                            MHD_HTTP_METHOD_GET);
646   handle->proc (handle->proc_cls,
647                 resp,
648                 MHD_HTTP_OK);
649   cleanup_handle (handle);
650 }
651
652
653 /**
654  * Function processing the REST call
655  *
656  * @param method HTTP method
657  * @param url URL of the HTTP request
658  * @param data body of the HTTP request (optional)
659  * @param data_size length of the body
660  * @param proc callback function for the result
661  * @param proc_cls closure for callback function
662  * @return GNUNET_OK if request accepted
663  */
664 static void
665 rest_gns_process_request(struct GNUNET_REST_RequestHandle *conndata_handle,
666                          GNUNET_REST_ResultProcessor proc,
667                          void *proc_cls)
668 {
669   struct LookupHandle *handle = GNUNET_new (struct LookupHandle);
670   struct GNUNET_REST_RequestHandlerError err;
671
672   handle->timeout = GNUNET_TIME_UNIT_FOREVER_REL;
673   handle->proc_cls = proc_cls;
674   handle->proc = proc;
675   handle->rest_handle = conndata_handle;
676
677   static const struct GNUNET_REST_RequestHandler handlers[] = {
678     {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_GNS, &get_gns_cont},
679     {MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_GNS, &options_cont},
680     GNUNET_REST_HANDLER_END
681   };
682
683   if (GNUNET_NO == GNUNET_JSONAPI_handle_request (conndata_handle,
684                                                   handlers,
685                                                   &err,
686                                                   handle))
687   {
688     handle->response_code = err.error_code;
689     GNUNET_SCHEDULER_add_now (&do_error, handle);
690   }
691 }
692
693
694 /**
695  * Entry point for the plugin.
696  *
697  * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
698  * @return NULL on error, otherwise the plugin context
699  */
700 void *
701 libgnunet_plugin_rest_gns_init (void *cls)
702 {
703   static struct Plugin plugin;
704   cfg = cls;
705   struct GNUNET_REST_Plugin *api;
706
707   if (NULL != plugin.cfg)
708     return NULL;                /* can only initialize once! */
709   memset (&plugin, 0, sizeof (struct Plugin));
710   plugin.cfg = cfg;
711   api = GNUNET_new (struct GNUNET_REST_Plugin);
712   api->cls = &plugin;
713   api->name = GNUNET_REST_API_NS_GNS;
714   api->process_request = &rest_gns_process_request;
715   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
716               _("GNS REST API initialized\n"));
717   return api;
718 }
719
720
721 /**
722  * Exit point from the plugin.
723  *
724  * @param cls the plugin context (as returned by "init")
725  * @return always NULL
726  */
727 void *
728 libgnunet_plugin_rest_gns_done (void *cls)
729 {
730   struct GNUNET_REST_Plugin *api = cls;
731   struct Plugin *plugin = api->cls;
732
733   plugin->cfg = NULL;
734   GNUNET_free (api);
735   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
736               "GNS REST plugin is finished\n");
737   return NULL;
738 }
739
740 /* end of plugin_rest_gns.c */