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