29b40088ddfeb5b4e365df0c1071903c9d4b9d88
[oweals/gnunet.git] / src / rest-plugins / plugin_rest_peerinfo.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 it
6    under the terms of the GNU Affero General Public License as published
7    by the Free Software Foundation, either version 3 of the License,
8    or (at your 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    Affero General Public License for more details.
14
15    You should have received a copy of the GNU Affero General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17    */
18 /**
19  * @author Martin Schanzenbach
20  * @author Philippe Buschmann
21  * @file peerinfo/plugin_rest_peerinfo.c
22  * @brief GNUnet Peerinfo REST plugin
23  */
24
25 #include "platform.h"
26 #include "gnunet_rest_plugin.h"
27 #include "gnunet_peerinfo_service.h"
28 #include "gnunet_transport_service.h"
29 #include "gnunet_rest_lib.h"
30 #include "gnunet_json_lib.h"
31 #include "microhttpd.h"
32 #include <jansson.h>
33
34 /**
35  * Peerinfo Namespace
36  */
37 #define GNUNET_REST_API_NS_PEERINFO "/peerinfo"
38
39 /**
40  * Peerinfo parameter peer
41  */
42 #define GNUNET_REST_PEERINFO_PEER "peer"
43
44 /**
45  * Peerinfo parameter friend
46  */
47 #define GNUNET_REST_PEERINFO_FRIEND "friend"
48
49 /**
50  * Peerinfo parameter array
51  */
52 #define GNUNET_REST_PEERINFO_ARRAY "array"
53
54 /**
55  * Error message Unknown Error
56  */
57 #define GNUNET_REST_PEERINFO_ERROR_UNKNOWN "Unknown Error"
58
59 /**
60  * How long until we time out during address lookup?
61  */
62 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
63 /**
64  * The configuration handle
65  */
66 const struct GNUNET_CONFIGURATION_Handle *cfg;
67
68 /**
69  * HTTP methods allows for this plugin
70  */
71 static char* allow_methods;
72
73 /**
74  * @brief struct returned by the initialization function of the plugin
75  */
76 struct Plugin
77 {
78   const struct GNUNET_CONFIGURATION_Handle *cfg;
79 };
80
81
82 /**
83  * Record we keep for each printable address.
84  */
85 struct AddressRecord
86 {
87   /**
88    * Current address-to-string context (if active, otherwise NULL).
89    */
90   struct GNUNET_TRANSPORT_AddressToStringContext *atsc;
91
92   /**
93    * Address expiration time
94    */
95   struct GNUNET_TIME_Absolute expiration;
96
97   /**
98    * Printable address.
99    */
100   char *result;
101
102   /**
103    * Print context this address record belongs to.
104    */
105   struct PrintContext *pc;
106 };
107
108
109 /**
110  * Structure we use to collect printable address information.
111  */
112 struct PrintContext
113 {
114   /**
115    * Kept in DLL.
116    */
117   struct PrintContext *next;
118
119   /**
120    * Kept in DLL.
121    */
122   struct PrintContext *prev;
123
124   /**
125    * Identity of the peer.
126    */
127   struct GNUNET_PeerIdentity peer;
128
129   /**
130    * List of printable addresses.
131    */
132   struct AddressRecord *address_list;
133
134   /**
135    * Number of completed addresses in @e address_list.
136    */
137   unsigned int num_addresses;
138
139   /**
140    * Number of addresses allocated in @e address_list.
141    */
142   unsigned int address_list_size;
143
144   /**
145    * Current offset in @e address_list (counted down).
146    */
147   unsigned int off;
148
149   /**
150    * Hello was friend only, #GNUNET_YES or #GNUNET_NO
151    */
152   int friend_only;
153
154   /**
155    * RequestHandle
156    */
157   struct RequestHandle *handle;
158
159 };
160
161 /**
162  * Head of list of print contexts.
163  */
164 static struct PrintContext *pc_head;
165
166 /**
167  * Tail of list of print contexts.
168  */
169 static struct PrintContext *pc_tail;
170
171 /**
172  * The request handle
173  */
174 struct RequestHandle
175 {
176   /**
177    * JSON temporary array
178    */
179   json_t *temp_array;
180
181   /**
182    * Expiration time string
183    */
184   char *expiration_str;
185
186   /**
187    * Address string
188    */
189   const char *address;
190
191   /**
192    * Iteration peer public key
193    */
194   char *pubkey;
195
196   /**
197    * JSON response
198    */
199   json_t *response;
200
201   /**
202    * Handle to PEERINFO it
203    */
204   struct GNUNET_PEERINFO_IteratorContext *list_it;
205
206   /**
207    * Handle to PEERINFO
208    */
209   struct GNUNET_PEERINFO_Handle *peerinfo_handle;
210
211   /**
212    * Rest connection
213    */
214   struct GNUNET_REST_RequestHandle *rest_handle;
215   
216   /**
217    * Desired timeout for the lookup (default is no timeout).
218    */
219   struct GNUNET_TIME_Relative timeout;
220
221   /**
222    * ID of a task associated with the resolution process.
223    */
224   struct GNUNET_SCHEDULER_Task *timeout_task;
225
226   /**
227    * The plugin result processor
228    */
229   GNUNET_REST_ResultProcessor proc;
230
231   /**
232    * The closure of the result processor
233    */
234   void *proc_cls;
235
236   /**
237    * The url
238    */
239   char *url;
240
241   /**
242    * Error response message
243    */
244   char *emsg;
245
246   /**
247    * Reponse code
248    */
249   int response_code;
250
251 };
252
253
254 /**
255  * Cleanup lookup handle
256  * @param handle Handle to clean up
257  */
258 static void
259 cleanup_handle (void *cls)
260 {
261   struct RequestHandle *handle = cls;
262
263   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
264               "Cleaning up\n");
265   if (NULL != handle->timeout_task)
266   {
267     GNUNET_SCHEDULER_cancel (handle->timeout_task);
268     handle->timeout_task = NULL;
269   }
270   if (NULL != handle->url)
271     GNUNET_free (handle->url);
272   if (NULL != handle->emsg)
273     GNUNET_free (handle->emsg);
274   if (NULL != handle->address)
275     GNUNET_free ((char*)handle->address);
276   if (NULL != handle->expiration_str)
277     GNUNET_free (handle->expiration_str);
278   if (NULL != handle->pubkey)
279     GNUNET_free (handle->pubkey);
280
281   if (NULL != handle->temp_array)
282   {
283     json_decref(handle->temp_array);
284     handle->temp_array = NULL;
285   }
286   if (NULL != handle->response)
287   {
288     json_decref(handle->response);
289     handle->response = NULL;
290   }
291
292   if (NULL != handle->list_it)
293   {
294     GNUNET_PEERINFO_iterate_cancel(handle->list_it);
295     handle->list_it = NULL;
296   }
297   if (NULL != handle->peerinfo_handle)
298   {
299     GNUNET_PEERINFO_disconnect(handle->peerinfo_handle);
300     handle->peerinfo_handle = NULL;
301   }
302   
303   GNUNET_free (handle);
304 }
305
306
307 /**
308  * Task run on errors.  Reports an error and cleans up everything.
309  *
310  * @param cls the `struct RequestHandle`
311  */
312 static void
313 do_error (void *cls)
314 {
315   struct RequestHandle *handle = cls;
316   struct MHD_Response *resp;
317   json_t *json_error = json_object();
318   char *response;
319
320   if (NULL == handle->emsg)
321     handle->emsg = GNUNET_strdup(GNUNET_REST_PEERINFO_ERROR_UNKNOWN);
322
323   json_object_set_new(json_error,"error", json_string(handle->emsg));
324
325   if (0 == handle->response_code)
326     handle->response_code = MHD_HTTP_OK;
327   response = json_dumps (json_error, 0);
328   resp = GNUNET_REST_create_response (response);
329   handle->proc (handle->proc_cls, resp, handle->response_code);
330   json_decref(json_error);
331   GNUNET_free(response);
332   GNUNET_SCHEDULER_add_now (&cleanup_handle, handle);
333 }
334
335
336 /**
337  * Function that assembles the response.
338  *
339  * @param cls the `struct RequestHandle`
340  */
341 static void
342 peerinfo_list_finished (void *cls)
343 {
344   struct RequestHandle *handle = cls;
345   char *result_str;
346   struct MHD_Response *resp;
347
348   if (NULL == handle->response)
349   {
350     handle->response_code = MHD_HTTP_NOT_FOUND;
351     handle->emsg = GNUNET_strdup ("No peers found");
352     GNUNET_SCHEDULER_add_now (&do_error, handle);
353     return;
354   }
355
356   result_str = json_dumps (handle->response, 0);
357   GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Result %s\n", result_str);
358   resp = GNUNET_REST_create_response (result_str);
359   handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
360   GNUNET_free_non_null (result_str);
361   GNUNET_SCHEDULER_add_now (&cleanup_handle, handle);
362 }
363
364
365 /**
366  * Iterator callback to go over all addresses and count them.
367  *
368  * @param cls `struct PrintContext *` with `off` to increment
369  * @param address the address
370  * @param expiration expiration time
371  * @return #GNUNET_OK to keep the address and continue
372  */
373 static int
374 count_address (void *cls,
375                const struct GNUNET_HELLO_Address *address,
376                struct GNUNET_TIME_Absolute expiration)
377 {
378   struct PrintContext *pc = cls;
379
380   if (0 == GNUNET_TIME_absolute_get_remaining (expiration).rel_value_us)
381   {
382     return GNUNET_OK;          /* ignore expired address */
383   }
384
385   pc->off++;
386   return GNUNET_OK;
387 }
388
389
390 /**
391  * Print the collected address information to the console and free @a pc.
392  *
393  * @param pc printing context
394  */
395 static void
396 dump_pc (struct PrintContext *pc)
397 {
398   struct RequestHandle *handle;
399   unsigned int i;
400   json_t *response_entry;
401   json_t *temp_array;
402   json_t *object;
403   json_t *address;
404   json_t *expires;
405   json_t *friend_and_peer_json;
406   char *friend_and_peer;
407
408   temp_array = json_array();
409   response_entry = json_object();
410
411   for (i = 0; i < pc->num_addresses; i++)
412   {
413     if (NULL != pc->address_list[i].result)
414     {
415       object = json_object ();
416       address = json_string(pc->address_list[i].result);
417       expires = json_string(
418           GNUNET_STRINGS_absolute_time_to_string (pc->address_list[i].expiration));
419       json_object_set (object, "address", address);
420       json_object_set (object, "expires", expires);
421
422       json_decref(address);
423       json_decref(expires);
424
425       json_array_append(temp_array, object);
426       json_decref(object);
427       GNUNET_free (pc->address_list[i].result);
428     }
429   }
430
431   if (0 < json_array_size(temp_array))
432   {
433     GNUNET_asprintf(&friend_and_peer,
434                     "%s%s",
435                     (GNUNET_YES == pc->friend_only) ? "F2F:" : "",
436                     GNUNET_i2s_full (&pc->peer));
437     friend_and_peer_json = json_string(friend_and_peer);
438     json_object_set(response_entry,
439                     GNUNET_REST_PEERINFO_PEER,
440                     friend_and_peer_json);
441     json_object_set(response_entry,
442                     GNUNET_REST_PEERINFO_ARRAY,
443                     temp_array);
444     json_array_append(pc->handle->response, response_entry);
445     json_decref(friend_and_peer_json);
446     GNUNET_free(friend_and_peer);
447   }
448
449   json_decref (temp_array);
450   json_decref(response_entry);
451
452   GNUNET_free_non_null (pc->address_list);
453   GNUNET_CONTAINER_DLL_remove (pc_head,
454                                pc_tail,
455                                pc);
456   handle = pc->handle;
457   GNUNET_free (pc);
458
459   if ( (NULL == pc_head) &&
460        (NULL == handle->list_it) )
461   {
462     GNUNET_SCHEDULER_add_now (&peerinfo_list_finished, handle);
463   }
464
465 }
466
467
468 /**
469  * Function to call with a human-readable format of an address
470  *
471  * @param cls closure
472  * @param address NULL on error, otherwise 0-terminated printable UTF-8 string
473  * @param res result of the address to string conversion:
474  *        if #GNUNET_OK: address was valid (conversion to
475  *                       string might still have failed)
476  *        if #GNUNET_SYSERR: address is invalid
477  */
478 static void
479 process_resolved_address (void *cls,
480                           const char *address,
481                           int res)
482 {
483   struct AddressRecord *ar = cls;
484   struct PrintContext *pc = ar->pc;
485
486   if (NULL != address)
487   {
488     if (0 != strlen (address))
489     {
490       if (NULL != ar->result)
491         GNUNET_free (ar->result);
492       ar->result = GNUNET_strdup (address);
493     }
494     return;
495   }
496   ar->atsc = NULL;
497   if (GNUNET_SYSERR == res)
498     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
499                 _("Failure: Cannot convert address to string for peer `%s'\n"),
500                 GNUNET_i2s (&ar->pc->peer));
501   pc->num_addresses++;
502   if (pc->num_addresses == pc->address_list_size)
503     dump_pc (ar->pc);
504 }
505
506
507 /**
508  * Iterator callback to go over all addresses.
509  *
510  * @param cls closure
511  * @param address the address
512  * @param expiration expiration time
513  * @return #GNUNET_OK to keep the address and continue
514  */
515 static int
516 print_address (void *cls,
517                const struct GNUNET_HELLO_Address *address,
518                struct GNUNET_TIME_Absolute expiration)
519 {
520   struct PrintContext *pc = cls;
521   struct AddressRecord *ar;
522
523   if (0 == GNUNET_TIME_absolute_get_remaining (expiration).rel_value_us)
524   {
525     return GNUNET_OK;          /* ignore expired address */
526   }
527
528   GNUNET_assert (0 < pc->off);
529   ar = &pc->address_list[--pc->off];
530   ar->pc = pc;
531   ar->expiration = expiration;
532   GNUNET_asprintf (&ar->result,
533                    "%s:%u:%u",
534                    address->transport_name,
535                    address->address_length,
536                    address->local_info);
537   ar->atsc = GNUNET_TRANSPORT_address_to_string (cfg,
538                                                  address,
539                                                  GNUNET_NO,
540                                                  TIMEOUT,
541                                                  &process_resolved_address,
542                                                  ar);
543   return GNUNET_OK;
544 }
545
546
547 /**
548  * Callback that processes each of the known HELLOs for the
549  * iteration response construction.
550  *
551  * @param cls closure, NULL
552  * @param peer id of the peer, NULL for last call
553  * @param hello hello message for the peer (can be NULL)
554  * @param err_msg message
555  */
556 void
557 peerinfo_list_iteration(void *cls,
558                         const struct GNUNET_PeerIdentity *peer,
559                         const struct GNUNET_HELLO_Message *hello,
560                         const char *err_msg)
561 {
562   struct RequestHandle *handle = cls;
563   struct PrintContext *pc;
564   int friend_only;
565
566   if (NULL == handle->response)
567   {
568     handle->response = json_array();
569   }
570
571   if (NULL == peer)
572   {
573     handle->list_it = NULL;
574     handle->emsg = GNUNET_strdup ("Error in communication with peerinfo");
575     if (NULL != err_msg)
576     {
577       GNUNET_free(handle->emsg);
578       handle->emsg = GNUNET_strdup (err_msg);
579       handle->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
580     }
581     if (NULL == pc_head)
582       GNUNET_SCHEDULER_add_now (&do_error, handle);
583     return;
584   }
585   if (NULL == hello)
586     return;
587
588   friend_only = GNUNET_NO;
589   if (NULL != hello)
590     friend_only = GNUNET_HELLO_is_friend_only (hello);
591
592   pc = GNUNET_new(struct PrintContext);
593   GNUNET_CONTAINER_DLL_insert (pc_head,
594                                pc_tail,
595                                pc);
596   pc->peer = *peer;
597   pc->friend_only = friend_only;
598   pc->handle = handle;
599   GNUNET_HELLO_iterate_addresses (hello,
600                                   GNUNET_NO,
601                                   &count_address,
602                                   pc);
603   if (0 == pc->off)
604   {
605     dump_pc (pc);
606     return;
607   }
608   pc->address_list_size = pc->off;
609   pc->address_list = GNUNET_malloc(
610       sizeof(struct AddressRecord) * pc->off);
611   GNUNET_HELLO_iterate_addresses (hello,
612                                   GNUNET_NO,
613                                   &print_address,
614                                   pc);
615 }
616
617 /**
618  * Handle peerinfo GET request
619  *
620  * @param con_handle the connection handle
621  * @param url the url
622  * @param cls the RequestHandle
623  */
624 void
625 peerinfo_get (struct GNUNET_REST_RequestHandle *con_handle,
626                  const char* url,
627                  void *cls)
628 {
629   struct RequestHandle *handle = cls;
630   struct GNUNET_HashCode key;
631   const struct GNUNET_PeerIdentity *specific_peer;
632   //GNUNET_PEER_Id peer_id;
633   int include_friend_only;
634   char* include_friend_only_str;
635
636   include_friend_only = GNUNET_NO;
637   GNUNET_CRYPTO_hash (GNUNET_REST_PEERINFO_FRIEND,
638                       strlen (GNUNET_REST_PEERINFO_FRIEND),
639                       &key);
640   if ( GNUNET_YES
641       == GNUNET_CONTAINER_multihashmap_contains (con_handle->url_param_map,
642                                                  &key))
643   {
644     include_friend_only_str = GNUNET_CONTAINER_multihashmap_get (
645               con_handle->url_param_map, &key);
646     if (0 == strcmp(include_friend_only_str, "yes"))
647     {
648       include_friend_only = GNUNET_YES;
649     }
650   }
651
652   specific_peer = NULL;
653   GNUNET_CRYPTO_hash (GNUNET_REST_PEERINFO_PEER,
654                       strlen (GNUNET_REST_PEERINFO_PEER),
655                       &key);
656   if ( GNUNET_YES
657       == GNUNET_CONTAINER_multihashmap_contains (con_handle->url_param_map,
658                                                  &key))
659   {
660     //peer_id = *(unsigned int*)GNUNET_CONTAINER_multihashmap_get (con_handle->url_param_map, &key);
661     //specific_peer = GNUNET_PEER_resolve2(peer_id);
662   }
663
664   handle->list_it = GNUNET_PEERINFO_iterate(handle->peerinfo_handle,
665                                             include_friend_only,
666                                             specific_peer,
667                                             &peerinfo_list_iteration,
668                                             handle);
669 }
670
671
672
673 /**
674  * Respond to OPTIONS request
675  *
676  * @param con_handle the connection handle
677  * @param url the url
678  * @param cls the RequestHandle
679  */
680 static void
681 options_cont (struct GNUNET_REST_RequestHandle *con_handle,
682               const char* url,
683               void *cls)
684 {
685   struct MHD_Response *resp;
686   struct RequestHandle *handle = cls;
687
688   //independent of path return all options
689   resp = GNUNET_REST_create_response (NULL);
690   MHD_add_response_header (resp,
691                            "Access-Control-Allow-Methods",
692                            allow_methods);
693   handle->proc (handle->proc_cls, resp, MHD_HTTP_OK);
694   GNUNET_SCHEDULER_add_now (&cleanup_handle, handle);
695   return;
696 }
697
698
699 /**
700  * Handle rest request
701  *
702  * @param handle the request handle
703  */
704 static void
705 init_cont (struct RequestHandle *handle)
706 {
707   struct GNUNET_REST_RequestHandlerError err;
708   static const struct GNUNET_REST_RequestHandler handlers[] = {
709     {MHD_HTTP_METHOD_GET, GNUNET_REST_API_NS_PEERINFO, &peerinfo_get},
710     {MHD_HTTP_METHOD_OPTIONS, GNUNET_REST_API_NS_PEERINFO, &options_cont},
711     GNUNET_REST_HANDLER_END
712   };
713
714   if (GNUNET_NO == GNUNET_REST_handle_request (handle->rest_handle,
715                                                handlers,
716                                                &err,
717                                                handle))
718   {
719     handle->response_code = err.error_code;
720     GNUNET_SCHEDULER_add_now (&do_error, handle);
721   }
722 }
723
724
725 /**
726  * Function processing the REST call
727  *
728  * @param method HTTP method
729  * @param url URL of the HTTP request
730  * @param data body of the HTTP request (optional)
731  * @param data_size length of the body
732  * @param proc callback function for the result
733  * @param proc_cls closure for callback function
734  * @return GNUNET_OK if request accepted
735  */
736 static void
737 rest_process_request(struct GNUNET_REST_RequestHandle *rest_handle,
738                               GNUNET_REST_ResultProcessor proc,
739                               void *proc_cls)
740 {
741   struct RequestHandle *handle = GNUNET_new (struct RequestHandle);
742   
743   handle->response_code = 0;
744   handle->timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60);
745   handle->proc_cls = proc_cls;
746   handle->proc = proc;
747   handle->rest_handle = rest_handle;
748   
749   handle->url = GNUNET_strdup (rest_handle->url);
750   if (handle->url[strlen (handle->url)-1] == '/')
751     handle->url[strlen (handle->url)-1] = '\0';
752   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting...\n");
753   handle->peerinfo_handle = GNUNET_PEERINFO_connect(cfg);
754   init_cont(handle);
755   handle->timeout_task =
756     GNUNET_SCHEDULER_add_delayed (handle->timeout,
757                                   &do_error,
758                                   handle);
759   
760   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connected\n");
761 }
762
763
764 /**
765  * Entry point for the plugin.
766  *
767  * @param cls Config info
768  * @return NULL on error, otherwise the plugin context
769  */
770 void *
771 libgnunet_plugin_rest_peerinfo_init (void *cls)
772 {
773   static struct Plugin plugin;
774   struct GNUNET_REST_Plugin *api;
775
776   cfg = cls;
777   if (NULL != plugin.cfg)
778     return NULL;                /* can only initialize once! */
779   memset (&plugin, 0, sizeof (struct Plugin));
780   plugin.cfg = cfg;
781   api = GNUNET_new (struct GNUNET_REST_Plugin);
782   api->cls = &plugin;
783   api->name = GNUNET_REST_API_NS_PEERINFO;
784   api->process_request = &rest_process_request;
785   GNUNET_asprintf (&allow_methods,
786                    "%s, %s, %s, %s, %s",
787                    MHD_HTTP_METHOD_GET,
788                    MHD_HTTP_METHOD_POST,
789                    MHD_HTTP_METHOD_PUT,
790                    MHD_HTTP_METHOD_DELETE,
791                    MHD_HTTP_METHOD_OPTIONS);
792
793   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
794               _("Peerinfo REST API initialized\n"));
795   return api;
796 }
797
798
799 /**
800  * Exit point from the plugin.
801  *
802  * @param cls the plugin context (as returned by "init")
803  * @return always NULL
804  */
805 void *
806 libgnunet_plugin_rest_peerinfo_done (void *cls)
807 {
808   struct GNUNET_REST_Plugin *api = cls;
809   struct Plugin *plugin = api->cls;
810   plugin->cfg = NULL;
811
812   GNUNET_free_non_null (allow_methods);
813   GNUNET_free (api);
814   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
815               "Peerinfo REST plugin is finished\n");
816   return NULL;
817 }
818
819 /* end of plugin_rest_peerinfo.c */
820