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