- add CORS logic
[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 API_NAMESPACE "/resolver"
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 /**
506  * Function processing the REST call
507  *
508  * @param method HTTP method
509  * @param url URL of the HTTP request
510  * @param data body of the HTTP request (optional)
511  * @param data_size length of the body
512  * @param proc callback function for the result
513  * @param proc_cls closure for callback function
514  * @return GNUNET_OK if request accepted
515  */
516 static void
517 rest_gns_process_request(struct RestConnectionDataHandle *conndata_handle,
518                          GNUNET_REST_ResultProcessor proc,
519                          void *proc_cls)
520 {
521   struct LookupHandle *handle = GNUNET_new (struct LookupHandle);
522   struct GNUNET_HashCode key;
523
524   handle->timeout = GNUNET_TIME_UNIT_FOREVER_REL;
525   handle->proc_cls = proc_cls;
526   handle->proc = proc;
527   //parse name and type from url
528   if (GNUNET_OK != parse_url (conndata_handle->url, handle))
529   {
530     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error parsing url...\n");
531     GNUNET_SCHEDULER_add_now (&do_error, handle);
532     return;
533   }
534   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
535               "Connecting...\n");
536   handle->gns = GNUNET_GNS_connect (cfg);
537   handle->identity = GNUNET_IDENTITY_connect (cfg, NULL, NULL);
538   handle->timeout_task = GNUNET_SCHEDULER_add_delayed (handle->timeout,
539                                                        &do_error, handle);
540   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
541               "Connected\n");
542   if (NULL == handle->gns)
543   {
544     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
545                 "Connecting to GNS failed\n");
546     GNUNET_SCHEDULER_add_now (&do_error, handle);
547     return;
548   }
549   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_OPTIONS,
550                       strlen (GNUNET_REST_JSONAPI_GNS_OPTIONS),
551                       &key);
552   handle->options = GNUNET_GNS_LO_DEFAULT;
553   if ( GNUNET_YES ==
554        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
555                                                &key) )
556   {
557     handle->options = GNUNET_GNS_LO_DEFAULT;//TODO(char*) GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
558                                                          //&key);
559   }
560   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_RECORD_TYPE,
561                       strlen (GNUNET_REST_JSONAPI_GNS_RECORD_TYPE),
562                       &key);
563   if ( GNUNET_YES ==
564        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
565                                                &key) )
566   {
567     handle->type = GNUNET_GNSRECORD_typename_to_number 
568       (GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
569       &key));
570   }
571   else
572     handle->type = GNUNET_GNSRECORD_TYPE_ANY;
573
574   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_PKEY,
575                       strlen (GNUNET_REST_JSONAPI_GNS_PKEY),
576                       &key);
577   if ( GNUNET_YES ==
578        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
579                                                &key) )
580   {
581     handle->pkey_str = GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
582                                                           &key);
583     if (GNUNET_OK !=
584         GNUNET_CRYPTO_ecdsa_public_key_from_string (handle->pkey_str,
585                                                     strlen(handle->pkey_str),
586                                                     &(handle->pkey)))
587     {
588       GNUNET_SCHEDULER_add_now (&do_error, handle);
589       return;
590     }
591     lookup_with_public_key (handle);
592     return;
593   }
594   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_EGO,
595                       strlen (GNUNET_REST_JSONAPI_GNS_EGO),
596                       &key);
597   if ( GNUNET_YES ==
598        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
599                                                &key) )
600   {
601     handle->ego_str = GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
602                                                          &key);
603     handle->el = GNUNET_IDENTITY_ego_lookup (cfg,
604                                              handle->ego_str,
605                                              &identity_zone_cb,
606                                              handle);
607     return;
608   }
609   if ( (NULL != handle->name) &&
610        (strlen (handle->name) > 4) &&
611        (0 == strcmp (".zkey",
612                      &handle->name[strlen (handle->name) - 4])) )
613   {
614     GNUNET_CRYPTO_ecdsa_key_get_public
615       (GNUNET_CRYPTO_ecdsa_key_get_anonymous (),
616        &(handle->pkey));
617     lookup_with_public_key (handle);
618   }
619   else
620   {
621     GNUNET_break (NULL == handle->id_op);
622     handle->id_op = GNUNET_IDENTITY_get (handle->identity,
623                                          "gns-master",
624                                          &identity_master_cb,
625                                          handle);
626     GNUNET_assert (NULL != handle->id_op);
627   }
628 }
629
630 /**
631  * Entry point for the plugin.
632  *
633  * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
634  * @return NULL on error, otherwise the plugin context
635  */
636 void *
637 libgnunet_plugin_rest_gns_init (void *cls)
638 {
639   static struct Plugin plugin;
640   cfg = cls;
641   struct GNUNET_REST_Plugin *api;
642
643   if (NULL != plugin.cfg)
644     return NULL;                /* can only initialize once! */
645   memset (&plugin, 0, sizeof (struct Plugin));
646   plugin.cfg = cfg;
647   api = GNUNET_new (struct GNUNET_REST_Plugin);
648   api->cls = &plugin;
649   api->name = API_NAMESPACE;
650   api->process_request = &rest_gns_process_request;
651   GNUNET_asprintf (&api->allow_methods, "%s", MHD_HTTP_METHOD_GET);
652   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
653               _("GNS REST API initialized\n"));
654   return api;
655 }
656
657
658 /**
659  * Exit point from the plugin.
660  *
661  * @param cls the plugin context (as returned by "init")
662  * @return always NULL
663  */
664 void *
665 libgnunet_plugin_rest_gns_done (void *cls)
666 {
667   struct GNUNET_REST_Plugin *api = cls;
668   struct Plugin *plugin = api->cls;
669
670   plugin->cfg = NULL;
671   GNUNET_free_non_null (api->allow_methods);
672   GNUNET_free (api);
673   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
674               "GNS REST plugin is finished\n");
675   return NULL;
676 }
677
678 /* end of plugin_rest_gns.c */