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