small API change: do no longer pass rarely needed GNUNET_SCHEDULER_TaskContext to...
[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 <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 {
216   struct LookupHandle *handle = cls;
217   struct MHD_Response *resp;
218
219   resp = GNUNET_REST_create_json_response (NULL);
220   handle->proc (handle->proc_cls, resp, MHD_HTTP_BAD_REQUEST);
221   cleanup_handle (handle);
222 }
223
224
225 /**
226  * Create json representation of a GNSRECORD
227  *
228  * @param rd the GNSRECORD_Data
229  */
230 static json_t *
231 gnsrecord_to_json (const struct GNUNET_GNSRECORD_Data *rd)
232 {
233   const char *typename;
234   char *string_val;
235   const char *exp_str;
236   json_t *record_obj;
237
238   typename = GNUNET_GNSRECORD_number_to_typename (rd->record_type);
239   string_val = GNUNET_GNSRECORD_value_to_string (rd->record_type,
240                                                  rd->data,
241                                                  rd->data_size);
242
243   if (NULL == string_val)
244   {
245     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
246                 "Record of type %d malformed, skipping\n",
247                 (int) rd->record_type);
248     return NULL;
249   }
250   record_obj = json_object();
251   json_object_set_new (record_obj, "type", json_string (typename));
252   json_object_set_new (record_obj, "value", json_string (string_val));
253   GNUNET_free (string_val);
254
255   if (GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION & rd->flags)
256   {
257     struct GNUNET_TIME_Relative time_rel;
258     time_rel.rel_value_us = rd->expiration_time;
259     exp_str = GNUNET_STRINGS_relative_time_to_string (time_rel, 1);
260   }
261   else
262   {
263     struct GNUNET_TIME_Absolute time_abs;
264     time_abs.abs_value_us = rd->expiration_time;
265     exp_str = GNUNET_STRINGS_absolute_time_to_string (time_abs);
266   }
267   json_object_set_new (record_obj, "expiration_time", json_string (exp_str));
268
269   json_object_set_new (record_obj, "expired",
270                        json_boolean (GNUNET_YES == GNUNET_GNSRECORD_is_expired (rd)));
271   return record_obj;
272 }
273
274 /**
275  * Function called with the result of a GNS lookup.
276  *
277  * @param cls the 'const char *' name that was resolved
278  * @param rd_count number of records returned
279  * @param rd array of @a rd_count records with the results
280  */
281 static void
282 process_lookup_result (void *cls, uint32_t rd_count,
283                        const struct GNUNET_GNSRECORD_Data *rd)
284 {
285   struct LookupHandle *handle = cls;
286   struct MHD_Response *resp;
287   struct JsonApiObject *json_object;
288   struct JsonApiResource *json_resource;
289   uint32_t i;
290   char *result;
291   json_t *result_array;
292   json_t *record_obj;
293
294   result_array = json_array();
295   json_object = GNUNET_REST_jsonapi_object_new ();
296   json_resource = GNUNET_REST_jsonapi_resource_new (GNUNET_REST_JSONAPI_GNS_TYPEINFO, handle->name);
297   handle->lookup_request = NULL;
298   for (i=0; i<rd_count; i++)
299   {
300     if ( (rd[i].record_type != handle->type) &&
301          (GNUNET_GNSRECORD_TYPE_ANY != handle->type) )
302       continue;
303     record_obj = gnsrecord_to_json (&(rd[i]));
304     json_array_append (result_array, record_obj);
305     json_decref (record_obj);
306   }
307   GNUNET_REST_jsonapi_resource_add_attr (json_resource,
308                                          GNUNET_REST_JSONAPI_GNS_RECORD,
309                                          result_array);
310   GNUNET_REST_jsonapi_object_resource_add (json_object, json_resource);
311   GNUNET_REST_jsonapi_data_serialize (json_object, &result);
312   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Result %s\n", result);
313   json_decref (result_array);
314   GNUNET_REST_jsonapi_object_delete (json_object);
315   resp = GNUNET_REST_create_json_response (result);
316   handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
317   GNUNET_free (result);
318   cleanup_handle (handle);
319 }
320
321
322 /**
323  * Perform the actual resolution, starting with the zone
324  * identified by the given public key and the shorten zone.
325  *
326  * @param pkey public key to use for the zone, can be NULL
327  * @param shorten_key private key used for shortening, can be NULL
328  */
329 static void
330 lookup_with_keys (struct LookupHandle *handle, const struct GNUNET_CRYPTO_EcdsaPrivateKey *shorten_key)
331 {
332   if (UINT32_MAX == handle->type)
333   {
334     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
335                 _("Invalid typename specified, assuming `ANY'\n"));
336     handle->type = GNUNET_GNSRECORD_TYPE_ANY;
337   }
338   if (NULL != handle->name)
339   {
340     handle->lookup_request = GNUNET_GNS_lookup (handle->gns,
341                                                 handle->name,
342                                                 &handle->pkey,
343                                                 handle->type,
344                                                 handle->options,
345                                                 shorten_key,
346                                                 &process_lookup_result,
347                                                 handle);
348   }
349   else
350   {
351     GNUNET_SCHEDULER_add_now (&do_error, handle);
352     return;
353   }
354 }
355
356 /**
357  * Method called to with the ego we are to use for shortening
358  * during the lookup.
359  *
360  * @param cls closure contains the public key to use
361  * @param ego ego handle, NULL if not found
362  * @param ctx context for application to store data for this ego
363  *                 (during the lifetime of this process, initially NULL)
364  * @param name name assigned by the user for this ego,
365  *                   NULL if the user just deleted the ego and it
366  *                   must thus no longer be used
367  */
368 static void
369 identity_shorten_cb (void *cls,
370                      struct GNUNET_IDENTITY_Ego *ego,
371                      void **ctx,
372                      const char *name)
373 {
374   struct LookupHandle *handle = cls;
375
376   handle->id_op = NULL;
377   if (NULL == ego)
378     lookup_with_keys (handle, NULL);
379   else
380     lookup_with_keys (handle,
381                       GNUNET_IDENTITY_ego_get_private_key (ego));
382 }
383
384 /**
385  * Perform the actual resolution, starting with the zone
386  * identified by the given public key.
387  *
388  * @param pkey public key to use for the zone
389  */
390 static void
391 lookup_with_public_key (struct LookupHandle *handle)
392 {
393   handle->pkeym = handle->pkey;
394   GNUNET_break (NULL == handle->id_op);
395   handle->id_op = GNUNET_IDENTITY_get (handle->identity,
396                                        "gns-short",
397                                        &identity_shorten_cb,
398                                        handle);
399   if (NULL == handle->id_op)
400   {
401     GNUNET_break (0);
402     lookup_with_keys (handle, NULL);
403   }
404 }
405
406 /**
407  * Method called to with the ego we are to use for the lookup,
408  * when the ego is determined by a name.
409  *
410  * @param cls closure (NULL, unused)
411  * @param ego ego handle, NULL if not found
412  */
413 static void
414 identity_zone_cb (void *cls,
415                   const struct GNUNET_IDENTITY_Ego *ego)
416 {
417   struct LookupHandle *handle = cls;
418
419   handle->el = NULL;
420   if (NULL == ego)
421   {
422     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
423                 _("Ego for not found, cannot perform lookup.\n"));
424     GNUNET_SCHEDULER_add_now (&do_error, handle);
425     return;
426   }
427   else
428   {
429     GNUNET_IDENTITY_ego_get_public_key (ego, &handle->pkey);
430     lookup_with_public_key (handle);
431   }
432   json_decref(handle->json_root);
433 }
434
435 /**
436  * Method called to with the ego we are to use for the lookup,
437  * when the ego is the one for the default master zone.
438  *
439  * @param cls closure (NULL, unused)
440  * @param ego ego handle, NULL if not found
441  * @param ctx context for application to store data for this ego
442  *                 (during the lifetime of this process, initially NULL)
443  * @param name name assigned by the user for this ego,
444  *                   NULL if the user just deleted the ego and it
445  *                   must thus no longer be used
446  */
447 static void
448 identity_master_cb (void *cls,
449                     struct GNUNET_IDENTITY_Ego *ego,
450                     void **ctx,
451                     const char *name)
452 {
453   const char *dot;
454   struct LookupHandle *handle = cls;
455
456   handle->id_op = NULL;
457   if (NULL == ego)
458   {
459     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
460                 _("Ego for `gns-master' not found, cannot perform lookup.  Did you run gnunet-gns-import.sh?\n"));
461     GNUNET_SCHEDULER_add_now (&do_error, handle);
462     return;
463   }
464   GNUNET_IDENTITY_ego_get_public_key (ego, &handle->pkey);
465   /* main name is our own master zone, do no look for that in the DHT */
466   handle->options = GNUNET_GNS_LO_LOCAL_MASTER;
467   /* if the name is of the form 'label.gnu', never go to the DHT */
468   dot = NULL;
469   if (NULL != handle->name)
470     dot = strchr (handle->name, '.');
471   if ( (NULL != dot) &&
472        (0 == strcasecmp (dot, ".gnu")) )
473     handle->options = GNUNET_GNS_LO_NO_DHT;
474   lookup_with_public_key (handle);
475 }
476
477 /**
478  * Parse REST uri for name and record type
479  *
480  * @param url Url to parse
481  * @param handle lookup handle to populate
482  * @return GNUNET_SYSERR on error
483  */
484 static int
485 parse_url (const char *url, struct LookupHandle *handle)
486 {
487   char *name;
488   char tmp_url[strlen(url)+1];
489   char *tok;
490
491   strcpy (tmp_url, url);
492   tok = strtok ((char*)tmp_url, "/");
493   if (NULL == tok)
494     return GNUNET_SYSERR;
495   name = strtok (NULL, "/");
496   if (NULL == name)
497     return GNUNET_SYSERR;
498   GNUNET_asprintf (&handle->name,
499                    "%s",
500                    name);
501   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
502               "Got name: %s\n", handle->name);
503   return GNUNET_OK;
504 }
505
506 static void
507 get_gns_cont (struct RestConnectionDataHandle *conndata_handle,
508               const char* url,
509               void *cls)
510 {
511   struct LookupHandle *handle = cls;
512   struct GNUNET_HashCode key;
513
514   //parse name and type from url
515   if (GNUNET_OK != parse_url (url, handle))
516   {
517     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error parsing url...\n");
518     GNUNET_SCHEDULER_add_now (&do_error, handle);
519     return;
520   }
521   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
522               "Connecting...\n");
523   handle->gns = GNUNET_GNS_connect (cfg);
524   handle->identity = GNUNET_IDENTITY_connect (cfg, NULL, NULL);
525   handle->timeout_task = GNUNET_SCHEDULER_add_delayed (handle->timeout,
526                                                        &do_error, handle);
527   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
528               "Connected\n");
529   if (NULL == handle->gns)
530   {
531     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
532                 "Connecting to GNS failed\n");
533     GNUNET_SCHEDULER_add_now (&do_error, handle);
534     return;
535   }
536   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_OPTIONS,
537                       strlen (GNUNET_REST_JSONAPI_GNS_OPTIONS),
538                       &key);
539   handle->options = GNUNET_GNS_LO_DEFAULT;
540   if ( GNUNET_YES ==
541        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
542                                                &key) )
543   {
544     handle->options = GNUNET_GNS_LO_DEFAULT;//TODO(char*) GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
545     //&key);
546   }
547   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_RECORD_TYPE,
548                       strlen (GNUNET_REST_JSONAPI_GNS_RECORD_TYPE),
549                       &key);
550   if ( GNUNET_YES ==
551        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
552                                                &key) )
553   {
554     handle->type = GNUNET_GNSRECORD_typename_to_number
555       (GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
556                                           &key));
557   }
558   else
559     handle->type = GNUNET_GNSRECORD_TYPE_ANY;
560
561   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_PKEY,
562                       strlen (GNUNET_REST_JSONAPI_GNS_PKEY),
563                       &key);
564   if ( GNUNET_YES ==
565        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
566                                                &key) )
567   {
568     handle->pkey_str = GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
569                                                           &key);
570     if (GNUNET_OK !=
571         GNUNET_CRYPTO_ecdsa_public_key_from_string (handle->pkey_str,
572                                                     strlen(handle->pkey_str),
573                                                     &(handle->pkey)))
574     {
575       GNUNET_SCHEDULER_add_now (&do_error, handle);
576       return;
577     }
578     lookup_with_public_key (handle);
579     return;
580   }
581   GNUNET_CRYPTO_hash (GNUNET_REST_JSONAPI_GNS_EGO,
582                       strlen (GNUNET_REST_JSONAPI_GNS_EGO),
583                       &key);
584   if ( GNUNET_YES ==
585        GNUNET_CONTAINER_multihashmap_contains (conndata_handle->url_param_map,
586                                                &key) )
587   {
588     handle->ego_str = GNUNET_CONTAINER_multihashmap_get (conndata_handle->url_param_map,
589                                                          &key);
590     handle->el = GNUNET_IDENTITY_ego_lookup (cfg,
591                                              handle->ego_str,
592                                              &identity_zone_cb,
593                                              handle);
594     return;
595   }
596   if ( (NULL != handle->name) &&
597        (strlen (handle->name) > 4) &&
598        (0 == strcmp (".zkey",
599                      &handle->name[strlen (handle->name) - 4])) )
600   {
601     GNUNET_CRYPTO_ecdsa_key_get_public
602       (GNUNET_CRYPTO_ecdsa_key_get_anonymous (),
603        &(handle->pkey));
604     lookup_with_public_key (handle);
605   }
606   else
607   {
608     GNUNET_break (NULL == handle->id_op);
609     handle->id_op = GNUNET_IDENTITY_get (handle->identity,
610                                          "gns-master",
611                                          &identity_master_cb,
612                                          handle);
613     GNUNET_assert (NULL != handle->id_op);
614   }
615 }
616
617 /**
618  * Handle rest request
619  *
620  * @param handle the lookup handle
621  */
622 static void
623 options_cont (struct RestConnectionDataHandle *con_handle,
624               const char* url,
625               void *cls)
626 {
627   struct MHD_Response *resp;
628   struct LookupHandle *handle = cls;
629
630   //For GNS, independent of path return all options
631   resp = GNUNET_REST_create_json_response (NULL);
632   MHD_add_response_header (resp,
633                            "Access-Control-Allow-Methods",
634                            MHD_HTTP_METHOD_GET);
635   handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
636   cleanup_handle (handle);
637   return;
638 }
639
640
641 /**
642  * Function processing the REST call
643  *
644  * @param method HTTP method
645  * @param url URL of the HTTP request
646  * @param data body of the HTTP request (optional)
647  * @param data_size length of the body
648  * @param proc callback function for the result
649  * @param proc_cls closure for callback function
650  * @return GNUNET_OK if request accepted
651  */
652 static void
653 rest_gns_process_request(struct RestConnectionDataHandle *conndata_handle,
654                          GNUNET_REST_ResultProcessor proc,
655                          void *proc_cls)
656 {
657   struct LookupHandle *handle = GNUNET_new (struct LookupHandle);
658
659   handle->timeout = GNUNET_TIME_UNIT_FOREVER_REL;
660   handle->proc_cls = proc_cls;
661   handle->proc = proc;
662
663   static const struct GNUNET_REST_RestConnectionHandler handlers[] = {
664     {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_GNS, &get_gns_cont},
665     {MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_GNS, &options_cont},
666     GNUNET_REST_HANDLER_END
667   };
668
669   if (GNUNET_NO == GNUNET_REST_handle_request (conndata_handle, handlers, handle))
670     GNUNET_SCHEDULER_add_now (&do_error, handle);
671 }
672
673
674
675 /**
676  * Entry point for the plugin.
677  *
678  * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
679  * @return NULL on error, otherwise the plugin context
680  */
681 void *
682 libgnunet_plugin_rest_gns_init (void *cls)
683 {
684   static struct Plugin plugin;
685   cfg = cls;
686   struct GNUNET_REST_Plugin *api;
687
688   if (NULL != plugin.cfg)
689     return NULL;                /* can only initialize once! */
690   memset (&plugin, 0, sizeof (struct Plugin));
691   plugin.cfg = cfg;
692   api = GNUNET_new (struct GNUNET_REST_Plugin);
693   api->cls = &plugin;
694   api->name = GNUNET_REST_API_NS_GNS;
695   api->process_request = &rest_gns_process_request;
696   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
697               _("GNS REST API initialized\n"));
698   return api;
699 }
700
701
702 /**
703  * Exit point from the plugin.
704  *
705  * @param cls the plugin context (as returned by "init")
706  * @return always NULL
707  */
708 void *
709 libgnunet_plugin_rest_gns_done (void *cls)
710 {
711   struct GNUNET_REST_Plugin *api = cls;
712   struct Plugin *plugin = api->cls;
713
714   plugin->cfg = NULL;
715   GNUNET_free (api);
716   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
717               "GNS REST plugin is finished\n");
718   return NULL;
719 }
720
721 /* end of plugin_rest_gns.c */