glitch in the license text detected by hyazinthe, thank you!
[oweals/gnunet.git] / src / peerinfo-tool / gnunet-peerinfo.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2001-2014, 2016 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
16 /**
17  * @file peerinfo-tool/gnunet-peerinfo.c
18  * @brief Print information about other known peers.
19  * @author Christian Grothoff
20  * @author Matthias Wachs
21  */
22 #include "platform.h"
23 #include "gnunet_util_lib.h"
24 #include "gnunet_hello_lib.h"
25 #include "gnunet_transport_service.h"
26 #include "gnunet_transport_hello_service.h"
27 #include "gnunet_peerinfo_service.h"
28 #include "gnunet-peerinfo_plugins.h"
29
30 /**
31  * How long until we time out during address lookup?
32  */
33 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
34
35 /**
36  * Structure we use to collect printable address information.
37  */
38 struct PrintContext;
39
40 /**
41  * Record we keep for each printable address.
42  */
43 struct AddressRecord
44 {
45   /**
46    * Current address-to-string context (if active, otherwise NULL).
47    */
48   struct GNUNET_TRANSPORT_AddressToStringContext *atsc;
49
50   /**
51    * Address expiration time
52    */
53   struct GNUNET_TIME_Absolute expiration;
54
55   /**
56    * Printable address.
57    */
58   char *result;
59
60   /**
61    * Print context this address record belongs to.
62    */
63   struct PrintContext *pc;
64 };
65
66
67 /**
68  * Structure we use to collect printable address information.
69  */
70 struct PrintContext
71 {
72
73   /**
74    * Kept in DLL.
75    */
76   struct PrintContext *next;
77
78   /**
79    * Kept in DLL.
80    */
81   struct PrintContext *prev;
82
83   /**
84    * Identity of the peer.
85    */
86   struct GNUNET_PeerIdentity peer;
87
88   /**
89    * List of printable addresses.
90    */
91   struct AddressRecord *address_list;
92
93   /**
94    * Number of completed addresses in @e address_list.
95    */
96   unsigned int num_addresses;
97
98   /**
99    * Number of addresses allocated in @e address_list.
100    */
101   unsigned int address_list_size;
102
103   /**
104    * Current offset in @e address_list (counted down).
105    */
106   unsigned int off;
107
108   /**
109    * Hello was friend only, #GNUNET_YES or #GNUNET_NO
110    */
111   int friend_only;
112
113 };
114
115
116 /**
117  * Option '-n'
118  */
119 static int no_resolve;
120
121 /**
122  * Option '-q'
123  */
124 static int be_quiet;
125
126 /**
127  * Option '-f'
128  */
129 static int include_friend_only;
130
131 /**
132  * Option '-s'
133  */
134 static int get_self;
135
136 /**
137  * Option
138  */
139 static int get_uri;
140
141 /**
142  * Option
143  */
144 static int default_operation;
145
146 /**
147  * Option '-i'
148  */
149 static int get_info;
150
151 /**
152  * Option
153  */
154 static char *put_uri;
155
156 /**
157  * Option -d
158  */
159 static char *dump_hello;
160
161 /**
162  * Handle to peerinfo service.
163  */
164 static struct GNUNET_PEERINFO_Handle *peerinfo;
165
166 /**
167  * Configuration handle.
168  */
169 static const struct GNUNET_CONFIGURATION_Handle *cfg;
170
171 /**
172  * Main state machine task (if active).
173  */
174 static struct GNUNET_SCHEDULER_Task *tt;
175
176 /**
177  * Pending #GNUNET_TRANSPORT_hello_get() operation.
178  */
179 static struct GNUNET_TRANSPORT_HelloGetHandle *gh;
180
181 /**
182  * Current iterator context (if active, otherwise NULL).
183  */
184 static struct GNUNET_PEERINFO_IteratorContext *pic;
185
186 /**
187  * My peer identity.
188  */
189 static struct GNUNET_PeerIdentity my_peer_identity;
190
191 /**
192  * Head of list of print contexts.
193  */
194 static struct PrintContext *pc_head;
195
196 /**
197  * Tail of list of print contexts.
198  */
199 static struct PrintContext *pc_tail;
200
201 /**
202  * Handle to current #GNUNET_PEERINFO_add_peer() operation.
203  */
204 static struct GNUNET_MQ_Envelope *ac;
205
206 /**
207  * Hello of this peer (if initialized).
208  */
209 static struct GNUNET_HELLO_Message *my_hello;
210
211
212 /**
213  * Main state machine that goes over all options and
214  * runs the next requested function.
215  *
216  * @param cls unused
217  */
218 static void
219 state_machine (void *cls);
220
221
222 /* ********************* 'get_info' ******************* */
223
224 /**
225  * Print the collected address information to the console and free @a pc.
226  *
227  * @param pc printing context
228  */
229 static void
230 dump_pc (struct PrintContext *pc)
231 {
232   unsigned int i;
233
234   printf (_("%sPeer `%s'\n"),
235           (GNUNET_YES == pc->friend_only) ? "F2F: " : "",
236           GNUNET_i2s_full (&pc->peer));
237   for (i = 0; i < pc->num_addresses; i++)
238   {
239     if (NULL != pc->address_list[i].result)
240     {
241       printf (_("\tExpires: %s \t %s\n"),
242               GNUNET_STRINGS_absolute_time_to_string (pc->address_list[i].expiration),
243               pc->address_list[i].result);
244       GNUNET_free (pc->address_list[i].result);
245     }
246   }
247   printf ("\n");
248   GNUNET_free_non_null (pc->address_list);
249   GNUNET_CONTAINER_DLL_remove (pc_head,
250                                pc_tail,
251                                pc);
252   GNUNET_free (pc);
253   if ( (NULL == pc_head) &&
254        (NULL == pic) )
255     tt = GNUNET_SCHEDULER_add_now (&state_machine,
256                                    NULL);
257 }
258
259
260 /* ************************* list all known addresses **************** */
261
262
263 /**
264  * Function to call with a human-readable format of an address
265  *
266  * @param cls closure
267  * @param address NULL on error, otherwise 0-terminated printable UTF-8 string
268  * @param res result of the address to string conversion:
269  *        if #GNUNET_OK: address was valid (conversion to
270  *                       string might still have failed)
271  *        if #GNUNET_SYSERR: address is invalid
272  */
273 static void
274 process_resolved_address (void *cls,
275                           const char *address,
276                           int res)
277 {
278   struct AddressRecord *ar = cls;
279   struct PrintContext *pc = ar->pc;
280
281   if (NULL != address)
282   {
283     if (0 != strlen (address))
284     {
285       if (NULL != ar->result)
286         GNUNET_free (ar->result);
287       ar->result = GNUNET_strdup (address);
288     }
289     return;
290   }
291   ar->atsc = NULL;
292   if (GNUNET_SYSERR == res)
293     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
294                 _("Failure: Cannot convert address to string for peer `%s'\n"),
295                 GNUNET_i2s (&ar->pc->peer));
296   pc->num_addresses++;
297   if (pc->num_addresses == pc->address_list_size)
298     dump_pc (pc);
299 }
300
301
302 /**
303  * Iterator callback to go over all addresses and count them.
304  *
305  * @param cls `struct PrintContext *` with `off` to increment
306  * @param address the address
307  * @param expiration expiration time
308  * @return #GNUNET_OK to keep the address and continue
309  */
310 static int
311 count_address (void *cls,
312                const struct GNUNET_HELLO_Address *address,
313                struct GNUNET_TIME_Absolute expiration)
314 {
315   struct PrintContext *pc = cls;
316
317   pc->off++;
318   return GNUNET_OK;
319 }
320
321
322 /**
323  * Iterator callback to go over all addresses.
324  *
325  * @param cls closure
326  * @param address the address
327  * @param expiration expiration time
328  * @return #GNUNET_OK to keep the address and continue
329  */
330 static int
331 print_address (void *cls,
332                const struct GNUNET_HELLO_Address *address,
333                struct GNUNET_TIME_Absolute expiration)
334 {
335   struct PrintContext *pc = cls;
336   struct AddressRecord *ar;
337
338   GNUNET_assert (0 < pc->off);
339   ar = &pc->address_list[--pc->off];
340   ar->pc = pc;
341   ar->expiration = expiration;
342   GNUNET_asprintf (&ar->result,
343                    "%s:%u:%u",
344                    address->transport_name,
345                    address->address_length,
346                    address->local_info);
347   ar->atsc = GNUNET_TRANSPORT_address_to_string (cfg,
348                                                  address,
349                                                  no_resolve,
350                                                  TIMEOUT,
351                                                  &process_resolved_address, ar);
352   return GNUNET_OK;
353 }
354
355
356 /**
357  * Print information about the peer.  Currently prints the `struct
358  * GNUNET_PeerIdentity` and the transport address.
359  *
360  * @param cls the `struct PrintContext *`
361  * @param peer identity of the peer
362  * @param hello addresses of the peer
363  * @param err_msg error message
364  */
365 static void
366 print_peer_info (void *cls,
367                  const struct GNUNET_PeerIdentity *peer,
368                  const struct GNUNET_HELLO_Message *hello,
369                  const char *err_msg)
370 {
371   struct PrintContext *pc;
372   int friend_only;
373
374   if (NULL == peer)
375   {
376     pic = NULL; /* end of iteration */
377     if (NULL != err_msg)
378     {
379       FPRINTF (stderr,
380                _("Error in communication with PEERINFO service: %s\n"),
381                err_msg);
382     }
383     if (NULL == pc_head)
384       tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
385     return;
386   }
387   friend_only = GNUNET_NO;
388   if (NULL != hello)
389         friend_only = GNUNET_HELLO_is_friend_only (hello);
390   if ( (GNUNET_YES == be_quiet) ||
391        (NULL == hello) )
392   {
393     printf ("%s%s\n",
394             (GNUNET_YES == friend_only) ? "F2F: " : "",
395             GNUNET_i2s_full (peer));
396     return;
397   }
398   pc = GNUNET_new (struct PrintContext);
399   GNUNET_CONTAINER_DLL_insert (pc_head,
400                                pc_tail,
401                                pc);
402   pc->peer = *peer;
403   pc->friend_only = friend_only;
404   GNUNET_HELLO_iterate_addresses (hello,
405                                   GNUNET_NO,
406                                   &count_address,
407                                   pc);
408   if (0 == pc->off)
409   {
410     dump_pc (pc);
411     return;
412   }
413   pc->address_list_size = pc->off;
414   pc->address_list = GNUNET_malloc (sizeof (struct AddressRecord) * pc->off);
415   GNUNET_HELLO_iterate_addresses (hello,
416                                   GNUNET_NO,
417                                   &print_address,
418                                   pc);
419 }
420
421 /* ************************* DUMP Hello  ************************** */
422
423 /**
424  * Count the number of addresses in the HELLO.
425  *
426  * @param cls pointer to an `int *` used for the counter
427  * @param address an address to count
428  * @param expiration (unused)
429  * @return #GNUNET_OK
430  */
431 static int
432 count_addr (void *cls,
433             const struct GNUNET_HELLO_Address *address,
434             struct GNUNET_TIME_Absolute expiration)
435 {
436   int *c = cls;
437
438   (*c)++;
439   return GNUNET_OK;
440 }
441
442
443 /**
444  * Write HELLO of my peer to a file.
445  *
446  * @param cls the `struct GetUriContext *`
447  * @param peer identity of the peer (unused)
448  * @param hello addresses of the peer
449  * @param err_msg error message
450  */
451 static void
452 dump_my_hello ()
453 {
454   unsigned int size;
455   unsigned int c_addr;
456
457   size = GNUNET_HELLO_size (my_hello);
458   if (0 == size)
459   {
460     FPRINTF (stderr,
461              _("Failure: Received invalid %s\n"),
462              "HELLO");
463     return;
464   }
465   if (GNUNET_SYSERR ==
466       GNUNET_DISK_fn_write (dump_hello,
467                             my_hello,
468                             size,
469                             GNUNET_DISK_PERM_USER_READ |
470                             GNUNET_DISK_PERM_USER_WRITE |
471                             GNUNET_DISK_PERM_GROUP_READ |
472                             GNUNET_DISK_PERM_OTHER_READ))
473   {
474     FPRINTF (stderr,
475              _("Failed to write HELLO with %u bytes to file `%s'\n"),
476              size,
477              dump_hello);
478     if (0 != UNLINK (dump_hello))
479       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
480                                 GNUNET_ERROR_TYPE_BULK,
481                                 "unlink",
482                                 dump_hello);
483
484   }
485   c_addr = 0;
486   GNUNET_HELLO_iterate_addresses (my_hello,
487                                   GNUNET_NO,
488                                   count_addr,
489                                   &c_addr);
490
491   if (! be_quiet)
492   {
493     FPRINTF (stderr,
494              _("Wrote %s HELLO containing %u addresses with %u bytes to file `%s'\n"),
495              (GNUNET_YES == GNUNET_HELLO_is_friend_only (my_hello)) ? "friend-only": "public",
496              c_addr,
497              size,
498              dump_hello);
499   }
500   GNUNET_free (dump_hello);
501   dump_hello = NULL;
502 }
503
504
505 /* ************************* GET URI ************************** */
506
507
508 /**
509  * Print URI of the peer.
510  *
511  * @param cls the `struct GetUriContext *`
512  * @param peer identity of the peer (unused)
513  * @param hello addresses of the peer
514  * @param err_msg error message
515  */
516 static void
517 print_my_uri (void *cls,
518               const struct GNUNET_PeerIdentity *peer,
519               const struct GNUNET_HELLO_Message *hello,
520               const char *err_msg)
521 {
522   char *uri;
523
524   if (NULL == peer)
525   {
526     pic = NULL;
527     if (NULL != err_msg)
528       FPRINTF (stderr,
529                _("Error in communication with PEERINFO service: %s\n"),
530                err_msg);
531     tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
532     return;
533   }
534
535   if (NULL == hello)
536     return;
537   uri = GNUNET_HELLO_compose_uri (hello,
538                                   &GPI_plugins_find);
539   if (NULL != uri)
540   {
541     printf ("%s\n",
542             (const char *) uri);
543     GNUNET_free (uri);
544   }
545 }
546
547
548 /* ************************* import HELLO by URI ********************* */
549
550
551 /**
552  * Continuation called from #GNUNET_PEERINFO_add_peer()
553  *
554  * @param cls closure, NULL
555  */
556 static void
557 add_continuation (void *cls)
558 {
559   ac = NULL;
560   tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
561 }
562
563
564 /**
565  * Parse the PUT URI given at the command line and add it to our peerinfo
566  * database.
567  *
568  * @param put_uri URI string to parse
569  * @return #GNUNET_OK on success,
570  *         #GNUNET_SYSERR if the URI was invalid,
571  *         #GNUNET_NO on other errors
572  */
573 static int
574 parse_hello_uri (const char *put_uri)
575 {
576   struct GNUNET_HELLO_Message *hello = NULL;
577
578   int ret = GNUNET_HELLO_parse_uri (put_uri,
579                                     &my_peer_identity.public_key,
580                                     &hello,
581                                     &GPI_plugins_find);
582
583   if (NULL != hello)
584   {
585     /* WARNING: this adds the address from URI WITHOUT verification! */
586     if (GNUNET_OK == ret)
587       ac = GNUNET_PEERINFO_add_peer (peerinfo,
588                                      hello,
589                                      &add_continuation,
590                                      NULL);
591     else
592       tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
593     GNUNET_free (hello);
594   }
595   return ret;
596 }
597
598
599 /* ************************ Main state machine ********************* */
600
601
602 /**
603  * Main state machine that goes over all options and
604  * runs the next requested function.
605  *
606  * @param cls unused
607  */
608 static void
609 shutdown_task (void *cls)
610 {
611   struct PrintContext *pc;
612   struct AddressRecord *ar;
613   unsigned int i;
614
615   if (NULL != ac)
616   {
617     GNUNET_MQ_send_cancel (ac);
618     ac = NULL;
619   }
620   if (NULL != tt)
621   {
622     GNUNET_SCHEDULER_cancel (tt);
623     tt = NULL;
624   }
625   if (NULL != pic)
626   {
627     GNUNET_PEERINFO_iterate_cancel (pic);
628     pic = NULL;
629   }
630   if (NULL != gh)
631   {
632     GNUNET_TRANSPORT_hello_get_cancel (gh);
633     gh = NULL;
634   }
635   while (NULL != (pc = pc_head))
636   {
637     GNUNET_CONTAINER_DLL_remove (pc_head,
638                                  pc_tail,
639                                  pc);
640     for (i=0;i<pc->address_list_size;i++)
641     {
642       ar = &pc->address_list[i];
643       GNUNET_free_non_null (ar->result);
644       if (NULL != ar->atsc)
645       {
646         GNUNET_TRANSPORT_address_to_string_cancel (ar->atsc);
647         ar->atsc = NULL;
648       }
649     }
650     GNUNET_free_non_null (pc->address_list);
651     GNUNET_free (pc);
652   }
653   GPI_plugins_unload ();
654   if (NULL != peerinfo)
655   {
656     GNUNET_PEERINFO_disconnect (peerinfo);
657     peerinfo = NULL;
658   }
659   if (NULL != my_hello)
660   {
661     GNUNET_free (my_hello);
662     my_hello = NULL;
663   }
664 }
665
666
667 /**
668  * Function called with our peer's HELLO message.
669  * Used to obtain our peer's public key.
670  *
671  * @param cls NULL
672  * @param hello the HELLO message
673  */
674 static void
675 hello_callback (void *cls,
676                 const struct GNUNET_MessageHeader *hello)
677 {
678   if (NULL == hello)
679   {
680     fprintf (stderr,
681              "Failed to get my own HELLO from this peer!\n");
682     GNUNET_SCHEDULER_shutdown ();
683     return;
684   }
685   my_hello = (struct GNUNET_HELLO_Message *) GNUNET_copy_message (hello);
686   GNUNET_assert (GNUNET_OK ==
687                  GNUNET_HELLO_get_id (my_hello,
688                                       &my_peer_identity));
689   GNUNET_TRANSPORT_hello_get_cancel (gh);
690   gh = NULL;
691   if (NULL != dump_hello)
692     dump_my_hello ();
693   tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
694 }
695
696
697 /**
698  * Main function that will be run by the scheduler.
699  *
700  * @param cls closure
701  * @param args remaining command-line arguments
702  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
703  * @param c configuration
704  */
705 static void
706 run (void *cls,
707      char *const *args,
708      const char *cfgfile,
709      const struct GNUNET_CONFIGURATION_Handle *c)
710 {
711   cfg = c;
712   if ( (NULL != args[0]) &&
713        (NULL == put_uri) &&
714        (args[0] == strcasestr (args[0],
715                                "gnunet://hello/")) )
716   {
717     put_uri = GNUNET_strdup (args[0]);
718     args++;
719   }
720   if (NULL != args[0])
721   {
722     FPRINTF (stderr,
723              _("Invalid command line argument `%s'\n"),
724              args[0]);
725     return;
726   }
727   if (NULL == (peerinfo = GNUNET_PEERINFO_connect (cfg)))
728   {
729     FPRINTF (stderr,
730              "%s",
731              "Could not access PEERINFO service.  Exiting.\n");
732     return;
733   }
734   if ( (GNUNET_YES == get_self) ||
735        (GNUNET_YES == get_uri) ||
736        (NULL != dump_hello) )
737   {
738     gh = GNUNET_TRANSPORT_hello_get (cfg,
739                                      GNUNET_TRANSPORT_AC_ANY,
740                                      &hello_callback,
741                                      NULL);
742   }
743   else
744   {
745     tt = GNUNET_SCHEDULER_add_now (&state_machine,
746                                    NULL);
747   }
748   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
749                                  NULL);
750 }
751
752
753 /**
754  * Main state machine that goes over all options and
755  * runs the next requested function.
756  *
757  * @param cls unused
758  */
759 static void
760 state_machine (void *cls)
761 {
762   tt = NULL;
763
764   if (NULL != put_uri)
765   {
766     GPI_plugins_load (cfg);
767     if (GNUNET_SYSERR == parse_hello_uri (put_uri))
768     {
769       fprintf (stderr,
770                _("Invalid URI `%s'\n"),
771                put_uri);
772       GNUNET_SCHEDULER_shutdown ();
773     }
774     GNUNET_free (put_uri);
775     put_uri = NULL;
776   }
777   else if (GNUNET_YES == get_info)
778   {
779     get_info = GNUNET_NO;
780     GPI_plugins_load (cfg);
781     pic = GNUNET_PEERINFO_iterate (peerinfo,
782                                    include_friend_only,
783                                    NULL,
784                                    &print_peer_info,
785                                    NULL);
786   }
787   else if (GNUNET_YES == get_self)
788   {
789     get_self = GNUNET_NO;
790     if (be_quiet)
791       printf ("%s\n",
792               GNUNET_i2s_full (&my_peer_identity));
793     else
794       printf (_("I am peer `%s'.\n"),
795               GNUNET_i2s_full (&my_peer_identity));
796     tt = GNUNET_SCHEDULER_add_now (&state_machine,
797                                    NULL);
798   }
799   else if (GNUNET_YES == get_uri)
800   {
801     GPI_plugins_load (cfg);
802     pic = GNUNET_PEERINFO_iterate (peerinfo,
803                                    include_friend_only,
804                                    &my_peer_identity,
805                                    &print_my_uri,
806                                    NULL);
807     get_uri = GNUNET_NO;
808   }
809   else if (GNUNET_YES == default_operation)
810   {
811     /* default operation list all */
812     default_operation = GNUNET_NO;
813     get_info = GNUNET_YES;
814     tt = GNUNET_SCHEDULER_add_now (&state_machine,
815                                    NULL);
816   }
817   else
818   {
819     GNUNET_SCHEDULER_shutdown ();
820   }
821   default_operation = GNUNET_NO;
822 }
823
824
825 /**
826  * The main function to obtain peer information.
827  *
828  * @param argc number of arguments from the command line
829  * @param argv command line arguments
830  * @return 0 ok, 1 on error
831  */
832 int
833 main (int argc, char *const *argv)
834 {
835   struct GNUNET_GETOPT_CommandLineOption options[] = {
836     GNUNET_GETOPT_option_flag ('n',
837                                   "numeric",
838                                   gettext_noop ("don't resolve host names"),
839                                   &no_resolve),
840
841     GNUNET_GETOPT_option_flag ('q',
842                                   "quiet",
843                                   gettext_noop ("output only the identity strings"),
844                                   &be_quiet),
845     GNUNET_GETOPT_option_flag ('f',
846                                   "friends",
847                                   gettext_noop ("include friend-only information"),
848                                   &include_friend_only),
849
850     GNUNET_GETOPT_option_flag ('s',
851                                   "self",
852                                   gettext_noop ("output our own identity only"),
853                                   &get_self),
854     
855     GNUNET_GETOPT_option_flag ('i',
856                                   "info",
857                                   gettext_noop ("list all known peers"),
858                                   &get_info),
859
860     GNUNET_GETOPT_option_string ('d',
861                                  "dump-hello",
862                                  NULL,
863                                  gettext_noop ("dump hello to file"),
864                                  &dump_hello),
865
866     GNUNET_GETOPT_option_flag ('g',
867                                   "get-hello",
868                                   gettext_noop ("also output HELLO uri(s)"),
869                                   &get_uri),
870
871     GNUNET_GETOPT_option_string ('p',
872                                  "put-hello",
873                                  "HELLO",
874                                  gettext_noop ("add given HELLO uri to the database"),
875                                  &put_uri),
876
877     GNUNET_GETOPT_OPTION_END
878   };
879   int ret;
880
881   default_operation = GNUNET_YES;
882   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc,
883                                                  argv,
884                                                  &argc,
885                                                  &argv))
886     return 2;
887
888   ret = (GNUNET_OK ==
889          GNUNET_PROGRAM_run (argc,
890                              argv,
891                              "gnunet-peerinfo",
892                              gettext_noop ("Print information about peers."),
893                              options,
894                              &run,
895                              NULL)) ? 0 : 1;
896   GNUNET_free ((void*) argv);
897   return ret;
898 }
899
900 /* end of gnunet-peerinfo.c */