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