remove 'illegal' (non-reentrant) log logic from signal handler
[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  * Head of list of print contexts.
164  */
165 static struct PrintContext *pc_head;
166
167 /**
168  * Tail of list of print contexts.
169  */
170 static struct PrintContext *pc_tail;
171
172 /**
173  * The request handle
174  */
175 struct RequestHandle
176 {
177   /**
178    * JSON temporary array
179    */
180   json_t *temp_array;
181
182   /**
183    * Expiration time string
184    */
185   char *expiration_str;
186
187   /**
188    * Address string
189    */
190   const char *address;
191
192   /**
193    * Iteration peer public key
194    */
195   char *pubkey;
196
197   /**
198    * JSON response
199    */
200   json_t *response;
201
202   /**
203    * Handle to PEERINFO it
204    */
205   struct GNUNET_PEERINFO_IteratorContext *list_it;
206
207   /**
208    * Handle to PEERINFO
209    */
210   struct GNUNET_PEERINFO_Handle *peerinfo_handle;
211
212   /**
213    * Rest connection
214    */
215   struct GNUNET_REST_RequestHandle *rest_handle;
216
217   /**
218    * Desired timeout for the lookup (default is no timeout).
219    */
220   struct GNUNET_TIME_Relative timeout;
221
222   /**
223    * ID of a task associated with the resolution process.
224    */
225   struct GNUNET_SCHEDULER_Task *timeout_task;
226
227   /**
228    * The plugin result processor
229    */
230   GNUNET_REST_ResultProcessor proc;
231
232   /**
233    * The closure of the result processor
234    */
235   void *proc_cls;
236
237   /**
238    * The url
239    */
240   char *url;
241
242   /**
243    * Error response message
244    */
245   char *emsg;
246
247   /**
248    * Reponse code
249    */
250   int response_code;
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 (
419           pc->address_list[i].expiration));
420       json_object_set (object, "address", address);
421       json_object_set (object, "expires", expires);
422
423       json_decref (address);
424       json_decref (expires);
425
426       json_array_append (temp_array, object);
427       json_decref (object);
428       GNUNET_free (pc->address_list[i].result);
429     }
430   }
431
432   if (0 < json_array_size (temp_array))
433   {
434     GNUNET_asprintf (&friend_and_peer,
435                      "%s%s",
436                      (GNUNET_YES == pc->friend_only) ? "F2F:" : "",
437                      GNUNET_i2s_full (&pc->peer));
438     friend_and_peer_json = json_string (friend_and_peer);
439     json_object_set (response_entry,
440                      GNUNET_REST_PEERINFO_PEER,
441                      friend_and_peer_json);
442     json_object_set (response_entry,
443                      GNUNET_REST_PEERINFO_ARRAY,
444                      temp_array);
445     json_array_append (pc->handle->response, response_entry);
446     json_decref (friend_and_peer_json);
447     GNUNET_free (friend_and_peer);
448   }
449
450   json_decref (temp_array);
451   json_decref (response_entry);
452
453   GNUNET_free_non_null (pc->address_list);
454   GNUNET_CONTAINER_DLL_remove (pc_head,
455                                pc_tail,
456                                pc);
457   handle = pc->handle;
458   GNUNET_free (pc);
459
460   if ((NULL == pc_head) &&
461       (NULL == handle->list_it))
462   {
463     GNUNET_SCHEDULER_add_now (&peerinfo_list_finished, handle);
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 /**
619  * Handle peerinfo GET request
620  *
621  * @param con_handle the connection handle
622  * @param url the url
623  * @param cls the RequestHandle
624  */
625 void
626 peerinfo_get (struct GNUNET_REST_RequestHandle *con_handle,
627               const char*url,
628               void *cls)
629 {
630   struct RequestHandle *handle = cls;
631   struct GNUNET_HashCode key;
632   const struct GNUNET_PeerIdentity *specific_peer;
633   // GNUNET_PEER_Id peer_id;
634   int include_friend_only;
635   char*include_friend_only_str;
636
637   include_friend_only = GNUNET_NO;
638   GNUNET_CRYPTO_hash (GNUNET_REST_PEERINFO_FRIEND,
639                       strlen (GNUNET_REST_PEERINFO_FRIEND),
640                       &key);
641   if (GNUNET_YES
642       == GNUNET_CONTAINER_multihashmap_contains (con_handle->url_param_map,
643                                                  &key))
644   {
645     include_friend_only_str = GNUNET_CONTAINER_multihashmap_get (
646       con_handle->url_param_map, &key);
647     if (0 == strcmp (include_friend_only_str, "yes"))
648     {
649       include_friend_only = GNUNET_YES;
650     }
651   }
652
653   specific_peer = NULL;
654   GNUNET_CRYPTO_hash (GNUNET_REST_PEERINFO_PEER,
655                       strlen (GNUNET_REST_PEERINFO_PEER),
656                       &key);
657   if (GNUNET_YES
658       == GNUNET_CONTAINER_multihashmap_contains (con_handle->url_param_map,
659                                                  &key))
660   {
661     // peer_id = *(unsigned int*)GNUNET_CONTAINER_multihashmap_get (con_handle->url_param_map, &key);
662     // specific_peer = GNUNET_PEER_resolve2(peer_id);
663   }
664
665   handle->list_it = GNUNET_PEERINFO_iterate (handle->peerinfo_handle,
666                                              include_friend_only,
667                                              specific_peer,
668                                              &peerinfo_list_iteration,
669                                              handle);
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,
745                                                    60);
746   handle->proc_cls = proc_cls;
747   handle->proc = proc;
748   handle->rest_handle = rest_handle;
749
750   handle->url = GNUNET_strdup (rest_handle->url);
751   if (handle->url[strlen (handle->url) - 1] == '/')
752     handle->url[strlen (handle->url) - 1] = '\0';
753   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting...\n");
754   handle->peerinfo_handle = GNUNET_PEERINFO_connect (cfg);
755   init_cont (handle);
756   handle->timeout_task =
757     GNUNET_SCHEDULER_add_delayed (handle->timeout,
758                                   &do_error,
759                                   handle);
760
761   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connected\n");
762 }
763
764
765 /**
766  * Entry point for the plugin.
767  *
768  * @param cls Config info
769  * @return NULL on error, otherwise the plugin context
770  */
771 void *
772 libgnunet_plugin_rest_peerinfo_init (void *cls)
773 {
774   static struct Plugin plugin;
775   struct GNUNET_REST_Plugin *api;
776
777   cfg = cls;
778   if (NULL != plugin.cfg)
779     return NULL;                /* can only initialize once! */
780   memset (&plugin, 0, sizeof(struct Plugin));
781   plugin.cfg = cfg;
782   api = GNUNET_new (struct GNUNET_REST_Plugin);
783   api->cls = &plugin;
784   api->name = GNUNET_REST_API_NS_PEERINFO;
785   api->process_request = &rest_process_request;
786   GNUNET_asprintf (&allow_methods,
787                    "%s, %s, %s, %s, %s",
788                    MHD_HTTP_METHOD_GET,
789                    MHD_HTTP_METHOD_POST,
790                    MHD_HTTP_METHOD_PUT,
791                    MHD_HTTP_METHOD_DELETE,
792                    MHD_HTTP_METHOD_OPTIONS);
793
794   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
795               _ ("Peerinfo REST API initialized\n"));
796   return api;
797 }
798
799
800 /**
801  * Exit point from the plugin.
802  *
803  * @param cls the plugin context (as returned by "init")
804  * @return always NULL
805  */
806 void *
807 libgnunet_plugin_rest_peerinfo_done (void *cls)
808 {
809   struct GNUNET_REST_Plugin *api = cls;
810   struct Plugin *plugin = api->cls;
811
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
822 /* end of plugin_rest_peerinfo.c */