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