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