6902e605cb77fd450e0a0ce1b946a76130dab1a6
[oweals/gnunet.git] / src / peerinfo-tool / gnunet-peerinfo.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2003, 2004, 2006, 2009, 2010 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_peerinfo_service.h"
31 #include "gnunet_transport_service.h"
32 #include "gnunet_program_lib.h"
33 #include "gnunet_transport_plugin.h"
34 #include "gnunet-peerinfo_plugins.h"
35
36
37 /**
38  * Structure we use to collect printable address information.
39  */
40 struct PrintContext
41 {
42   /**
43    * Identity of the peer.
44    */
45   struct GNUNET_PeerIdentity peer;
46   
47   /**
48    * List of printable addresses.
49    */
50   char **address_list;
51
52   /**
53    * Number of addresses in 'address_list'.
54    */
55   unsigned int num_addresses;
56
57   /**
58    * Current offset in 'address_list'
59    */
60   uint32_t off;
61
62   /**
63    * URI (FIXME: overloaded struct!)
64    */
65   char *uri;
66
67   /**
68    * Length of 'uri' (FIXME: not nice)
69    */
70   size_t uri_len;
71 };
72
73
74 /**
75  * FIXME.
76  */
77 struct GNUNET_PEERINFO_HelloAddressParsingContext
78 {
79   /**
80    * FIXME.
81    */
82   char *tmp;
83   
84   /**
85    * FIXME.
86    */
87   char *pos;
88
89   /**
90    * FIXME.
91    */
92   size_t tmp_len;
93 };
94
95
96 /**
97  * Option '-n'
98  */
99 static int no_resolve;
100
101 /**
102  * Option '-q'
103  */
104 static int be_quiet;
105
106 /**
107  * Option '-s'
108  */
109 static int get_self;
110
111 /**
112  * Option 
113  */
114 static int get_uri;
115
116 /**
117  * Option '-i'
118  */
119 static int get_info;
120
121 /**
122  * Option 
123  */
124 static char *put_uri;
125
126 /**
127  * Handle to peerinfo service.
128  */
129 static struct GNUNET_PEERINFO_Handle *peerinfo;
130
131 /**
132  * Configuration handle.
133  */
134 static const struct GNUNET_CONFIGURATION_Handle *cfg;
135
136 /**
137  * Main state machine task (if active).
138  */
139 static GNUNET_SCHEDULER_TaskIdentifier tt;
140
141 /**
142  * Current iterator context (if active, otherwise NULL).
143  */
144 static struct GNUNET_PEERINFO_IteratorContext *pic;
145
146 /**
147  * Current address-to-string context (if active, otherwise NULL).
148  */
149 static struct GNUNET_TRANSPORT_AddressToStringContext *atsc;
150
151 /**
152  * My peer identity.
153  */
154 static struct GNUNET_PeerIdentity my_peer_identity;
155
156 /**
157  * My public key.
158  */
159 static struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded my_public_key;
160
161
162 /**
163  * Main state machine that goes over all options and
164  * runs the next requested function.
165  *
166  * @param cls unused
167  * @param tc unused
168  */
169 static void
170 state_machine (void *cls,
171                const struct GNUNET_SCHEDULER_TaskContext *tc);
172
173
174 /* ********************* 'get_info' ******************* */
175
176 /**
177  * Print the collected address information to the console and free 'pc'.
178  *
179  * @param pc printing context
180  */
181 static void
182 dump_pc (struct PrintContext *pc)
183 {
184   struct GNUNET_CRYPTO_HashAsciiEncoded enc;
185   unsigned int i;
186
187   GNUNET_CRYPTO_hash_to_enc (&pc->peer.hashPubKey, &enc);
188   printf (_("Peer `%s'\n"), (const char *) &enc);
189   for (i = 0; i < pc->num_addresses; i++)
190   {
191     printf ("\t%s\n", pc->address_list[i]);
192     GNUNET_free (pc->address_list[i]);
193   }
194   printf ("\n");
195   GNUNET_array_grow (pc->address_list, pc->num_addresses, 0);
196   GNUNET_free (pc);
197 }
198
199
200 /**
201  * Function to call with a human-readable format of an address
202  *
203  * @param cls closure
204  * @param address NULL on error, otherwise 0-terminated printable UTF-8 string
205  */
206 static void
207 process_resolved_address (void *cls, const char *address)
208 {
209   struct PrintContext *pc = cls;
210
211   atsc = NULL;
212   if (address == NULL)
213   {
214     pc->off--;
215     if (pc->off == 0)
216       dump_pc (pc);
217     return;
218   }
219   GNUNET_array_append (pc->address_list, pc->num_addresses,
220                        GNUNET_strdup (address));
221 }
222
223
224 /**
225  * Iterator callback to go over all addresses.
226  *
227  * @param cls closure
228  * @param address the address
229  * @param expiration expiration time
230  * @return GNUNET_OK to keep the address and continue
231  */
232 static int
233 count_address (void *cls, const struct GNUNET_HELLO_Address *address,
234                struct GNUNET_TIME_Absolute expiration)
235 {
236   struct PrintContext *pc = cls;
237
238   pc->off++;
239   return GNUNET_OK;
240 }
241
242
243 /**
244  * Iterator callback to go over all addresses.
245  *
246  * @param cls closure
247  * @param address the address
248  * @param expiration expiration time
249  * @return GNUNET_OK to keep the address and continue
250  */
251 static int
252 print_address (void *cls, const struct GNUNET_HELLO_Address *address,
253                struct GNUNET_TIME_Absolute expiration)
254 {
255   struct PrintContext *pc = cls;
256
257   // FIXME: this is called many times in parallel!
258   atsc = GNUNET_TRANSPORT_address_to_string (cfg, address, no_resolve,
259                                              GNUNET_TIME_relative_multiply
260                                              (GNUNET_TIME_UNIT_SECONDS, 10),
261                                              &process_resolved_address, pc);
262   return GNUNET_OK;
263 }
264
265
266 /**
267  * Print information about the peer.
268  * Currently prints the GNUNET_PeerIdentity and the IP.
269  * Could of course do more (e.g. resolve via DNS).
270  */
271 static void
272 print_peer_info (void *cls, const struct GNUNET_PeerIdentity *peer,
273                  const struct GNUNET_HELLO_Message *hello, const char *err_msg)
274 {
275   struct GNUNET_CRYPTO_HashAsciiEncoded enc;
276   struct PrintContext *pc;
277
278   if (peer == NULL)
279   {
280     if (err_msg != NULL)
281       FPRINTF (stderr, "%s",  _("Error in communication with PEERINFO service\n"));
282     // FIXME: this doesn't mean we're fully done with the printing!
283     // (as the a2s calls happen asynchronously!)
284     tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
285     return;
286   }
287   if ((be_quiet) || (NULL == hello))
288   {
289     GNUNET_CRYPTO_hash_to_enc (&peer->hashPubKey, &enc);
290     printf ("%s\n", (const char *) &enc);
291     return;
292   }
293   pc = GNUNET_malloc (sizeof (struct PrintContext));
294   pc->peer = *peer;
295   GNUNET_HELLO_iterate_addresses (hello, GNUNET_NO, &count_address, pc);
296   if (0 == pc->off)
297   {
298     dump_pc (pc);
299     return;
300   }
301   GNUNET_HELLO_iterate_addresses (hello, GNUNET_NO, &print_address, pc);
302 }
303
304
305
306
307
308
309
310
311
312
313
314
315 static int
316 compose_uri (void *cls, const struct GNUNET_HELLO_Address *address,
317              struct GNUNET_TIME_Absolute expiration)
318 {
319   struct PrintContext *pc = cls;
320   struct GNUNET_TRANSPORT_PluginFunctions *papi;
321   static const char *addr;
322
323   papi = GPI_plugins_find (address->transport_name);
324   if (papi == NULL)
325   {
326     /* Not an error - we might just not have the right plugin. */
327     return GNUNET_OK;
328   }
329
330   addr = papi->address_to_string (papi->cls, address->address, address->address_length);
331   if (addr != NULL)
332   {
333     ssize_t l = strlen (addr);
334     if (l > 0)
335     {
336       struct tm *t;
337       time_t seconds;
338       int s;
339       seconds = expiration.abs_value / 1000;
340       t = gmtime(&seconds);
341       pc->uri = GNUNET_realloc (pc->uri, pc->uri_len + 1 + 14 + 1 + strlen (address->transport_name) + 1 + l + 1 /* 0 */);
342       s = sprintf (&pc->uri[pc->uri_len], "!%04u%02u%02u%02u%02u%02u!%s!%s",
343           t->tm_year, t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec,
344           address->transport_name, addr);
345       if (s > 0)
346         pc->uri_len += s;
347     }
348   }
349   return GNUNET_OK;
350 }
351
352
353 /**
354  * Print information about the peer.
355  */
356 static void
357 print_my_uri (void *cls, const struct GNUNET_PeerIdentity *peer,
358               const struct GNUNET_HELLO_Message *hello, const char *err_msg)
359 {
360   struct GNUNET_CRYPTO_HashAsciiEncoded enc;
361   struct PrintContext *pc = cls;
362
363   if (peer == NULL)
364   {
365     if (err_msg != NULL)
366       FPRINTF (stderr, "%s",  _("Error in communication with PEERINFO service\n"));
367     GNUNET_free (pc->uri);
368     return;
369   }
370   if ((be_quiet) || (NULL == hello))
371   {
372     GNUNET_CRYPTO_hash_to_enc (&peer->hashPubKey, &enc);
373     printf ("%s\n", (const char *) &enc);
374     if (be_quiet && get_uri != GNUNET_YES)
375       return;
376   }
377   pc->peer = *peer;
378   if (NULL != hello)
379   {
380     GNUNET_HELLO_iterate_addresses (hello, GNUNET_NO, &count_address, pc);
381     GNUNET_HELLO_iterate_addresses (hello, GNUNET_NO, &compose_uri, pc);
382   }
383   printf ("%s\n", pc->uri);
384 }
385
386
387
388 static size_t
389 add_addr_to_hello (void *cls, size_t max, void *buffer)
390 {
391   struct tm expiration_time;
392   char buf[5];
393   long l;
394   time_t expiration_seconds;
395   struct GNUNET_TIME_Absolute expire;
396
397   struct GNUNET_PEERINFO_HelloAddressParsingContext *ctx = cls;
398   char *exp1, *exp2;
399   struct GNUNET_TRANSPORT_PluginFunctions *papi;
400   void *addr;
401   size_t addr_len;
402
403   /* End of string */
404   if (ctx->pos - ctx->tmp == ctx->tmp_len)
405     return 0;
406
407   /* Parsed past the end of string, OR wrong format */
408   if ((ctx->pos - ctx->tmp > ctx->tmp_len) || ctx->pos[0] != '!')
409   {
410     GNUNET_break (0);
411     return 0;
412   }
413
414   /* Not enough bytes (3 for three '!', 14 for expiration date, and
415    * at least 1 for type and 1 for address (1-byte long address is a joke,
416    * but it is not completely unrealistic. Zero-length address is.
417    */
418   if (ctx->tmp_len - (ctx->pos - ctx->tmp) < 1 /*!*/ * 3 + 14 + /* at least */ 2)
419   {
420     GNUNET_break (0);
421     return 0;
422   }
423   /* Go past the first '!', now we're on expiration date */
424   ctx->pos += 1;
425   /* Its length is known, so check for the next '!' right away */
426   if (ctx->pos[14] != '!')
427   {
428     GNUNET_break (0);
429     return 0;
430   }
431
432   memset (&expiration_time, 0, sizeof (struct tm));
433
434   /* This is FAR more strict than strptime(ctx->pos, "%Y%m%d%H%M%S", ...); */
435   /* FIXME: make it a separate function, since expiration is specified to every address */
436 #define GETNDIGITS(n,cond) \
437   strncpy (buf, &ctx->pos[0], n); \
438   buf[n] = '\0'; \
439   errno = 0; \
440   l = strtol (buf, NULL, 10); \
441   if (errno != 0 || cond) \
442   { \
443     GNUNET_break (0); \
444     return 0; \
445   } \
446   ctx->pos += n;
447
448   GETNDIGITS (4, l < 1900)
449   expiration_time.tm_year = l - 1900;
450
451   GETNDIGITS (2, l < 1 || l > 12)
452   expiration_time.tm_mon = l;
453
454   GETNDIGITS (2, l < 1 || l > 31)
455   expiration_time.tm_mday = l;
456
457   GETNDIGITS (2, l < 0 || l > 23)
458   expiration_time.tm_hour = l;
459
460   GETNDIGITS (2, l < 0 || l > 59)
461   expiration_time.tm_min = l;
462
463   /* 60 - with a leap second */
464   GETNDIGITS (2, l < 0 || l > 60)
465   expiration_time.tm_sec = l;
466
467   expiration_time.tm_isdst = -1;
468
469 #undef GETNDIGITS
470
471   expiration_seconds = mktime (&expiration_time);
472   if (expiration_seconds == (time_t) -1)
473   {
474     GNUNET_break (0);
475     return 0;
476   }
477   expire.abs_value = expiration_seconds * 1000;
478
479   /* Now we're at '!', advance to the transport type */
480   ctx->pos += 1;
481
482   /* Find the next '!' that separates transport type from
483    * the address
484    */
485   exp1 = strstr (ctx->pos, "!");
486   if (exp1 == NULL)
487   {
488     GNUNET_break (0);
489     return 0;
490   }
491   /* We need it 0-terminated */
492   exp1[0] = '\0';
493   /* Find the '!' that separates address from the next record.
494    * It might not be there, if this is the last record.
495    */
496   exp2 = strstr (&exp1[1], "!");
497   if (exp2 == NULL)
498     exp2 = &ctx->tmp[ctx->tmp_len];
499
500   papi = GPI_plugins_find (ctx->pos);
501   if (papi == NULL)
502   {
503     /* Not an error - we might just not have the right plugin.
504      * Skip this part, advance to the next one and recurse.
505      * But only if this is not the end of string.
506      */
507     ctx->pos = exp2 + 1;
508     if (ctx->pos - ctx->tmp >= ctx->tmp_len)
509       return 0;
510     return add_addr_to_hello (cls, max, buffer);
511   }
512   if (NULL == papi->string_to_address)
513   {
514     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
515                 _("Plugin `%s' does not support URIs yet\n"),
516                 ctx->pos);
517     ctx->pos = exp2 + 1;
518     if (ctx->pos - ctx->tmp >= ctx->tmp_len)
519       return 0;
520     return add_addr_to_hello (cls, max, buffer);
521   }
522   if (GNUNET_OK == papi->string_to_address (papi->cls, &exp1[1], exp2 - &exp1[1], &addr, &addr_len))
523   {
524     struct GNUNET_HELLO_Address address;
525     int ret;
526
527     /* address.peer is unset - not used by add_address() */
528     address.address_length = addr_len;
529     address.address = addr;
530     address.transport_name = ctx->pos;
531     ret = GNUNET_HELLO_add_address (&address, expire, buffer, max);
532     GNUNET_free (addr);
533     ctx->pos = exp2;
534     return ret;
535   }
536   return 0;
537 }
538
539
540 static void
541 parse_hello (const struct GNUNET_CONFIGURATION_Handle *c,
542              const char *put_uri)
543 {
544   int r;
545   char *scheme_part = NULL;
546   char *path_part = NULL;
547   char *exc;
548   int std_result;
549   struct GNUNET_HELLO_Message *hello;
550   struct GNUNET_PEERINFO_HelloAddressParsingContext ctx;
551
552   r = GNUNET_STRINGS_parse_uri (put_uri, &scheme_part, (const char **) &path_part);
553   if (r == GNUNET_NO)
554     return;
555   if (scheme_part == NULL || strcmp (scheme_part, "gnunet://") != 0)
556   {
557     GNUNET_free_non_null (scheme_part);
558     return;
559   }
560   GNUNET_free (scheme_part);
561
562   if (strncmp (path_part, "hello/", 6) != 0)
563     return;
564
565   path_part = &path_part[6];
566   ctx.tmp = GNUNET_strdup (path_part);
567   ctx.tmp_len = strlen (path_part);
568   exc = strstr (ctx.tmp, "!");
569   if (exc == NULL)
570     exc = ctx.tmp + ctx.tmp_len;
571   ctx.pos = exc;
572
573   std_result = GNUNET_STRINGS_string_to_data (ctx.tmp, exc - ctx.tmp,
574       (unsigned char *) &my_public_key, sizeof (my_public_key));
575   if (std_result != GNUNET_OK)
576   {
577     GNUNET_free (ctx.tmp);
578     return;
579   }
580
581   hello = GNUNET_HELLO_create (&my_public_key, add_addr_to_hello, &ctx);
582   GNUNET_free (ctx.tmp);
583
584   /* WARNING: this adds the address from URI WITHOUT verification! */
585   GNUNET_PEERINFO_add_peer (peerinfo, hello);
586   GNUNET_free (hello);
587   /* wait 1s to give peerinfo operation a chance to succeed */
588   tt = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
589                                      &state_machine, NULL);
590 }
591
592
593 /**
594  * Main state machine that goes over all options and
595  * runs the next requested function.
596  *
597  * @param cls unused
598  * @param tc scheduler context
599  */
600 static void
601 shutdown_task (void *cls,
602                const struct GNUNET_SCHEDULER_TaskContext *tc)
603 {
604   if (GNUNET_SCHEDULER_NO_TASK != tt)
605   {
606     GNUNET_SCHEDULER_cancel (tt);
607     tt = GNUNET_SCHEDULER_NO_TASK;
608   }
609   if (NULL != pic)
610   {
611     GNUNET_PEERINFO_iterate_cancel (pic);
612     pic = NULL;
613   }
614   if (NULL != atsc)
615   {
616     GNUNET_TRANSPORT_address_to_string_cancel (atsc);
617     atsc = NULL;
618   }
619   GPI_plugins_unload ();
620   GNUNET_PEERINFO_disconnect (peerinfo);
621   peerinfo = NULL;
622 }
623
624
625 /**
626  * Main function that will be run by the scheduler.
627  *
628  * @param cls closure
629  * @param args remaining command-line arguments
630  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
631  * @param c configuration
632  */
633 static void
634 run (void *cls, char *const *args, const char *cfgfile,
635      const struct GNUNET_CONFIGURATION_Handle *c)
636 {
637   struct GNUNET_CRYPTO_RsaPrivateKey *priv;
638   char *fn;
639
640   cfg = c;
641   if (args[0] != NULL)
642   {
643     FPRINTF (stderr, _("Invalid command line argument `%s'\n"), args[0]);
644     return;
645   }
646   peerinfo = GNUNET_PEERINFO_connect (cfg);
647   if (peerinfo == NULL)
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) )
653   {
654     /* load private key */
655     if (GNUNET_OK !=
656         GNUNET_CONFIGURATION_get_value_filename (cfg, "GNUNETD", "HOSTKEY",
657                                                  &fn))
658     {
659       FPRINTF (stderr, _("Could not find option `%s:%s' in configuration.\n"),
660                "GNUNETD", "HOSTKEYFILE");
661       return;
662     }
663
664     if (NULL == (priv = GNUNET_CRYPTO_rsa_key_create_from_file (fn)))
665     {
666       FPRINTF (stderr, _("Loading hostkey from `%s' failed.\n"), fn);
667       GNUNET_free (fn);
668       return;
669     }
670     GNUNET_free (fn);
671     GNUNET_CRYPTO_rsa_key_get_public (priv, &my_public_key);
672     GNUNET_CRYPTO_rsa_key_free (priv);
673     GNUNET_CRYPTO_hash (&my_public_key, sizeof (my_public_key), &my_peer_identity.hashPubKey);
674   }
675
676   tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
677   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
678                                 &shutdown_task,
679                                 NULL);
680 }
681
682
683 /**
684  * Main state machine that goes over all options and
685  * runs the next requested function.
686  *
687  * @param cls unused
688  * @param tc scheduler context
689  */
690 static void
691 state_machine (void *cls,
692                const struct GNUNET_SCHEDULER_TaskContext *tc)
693 {
694   tt = GNUNET_SCHEDULER_NO_TASK;
695
696   if (NULL != put_uri)
697   {
698     GPI_plugins_load (cfg);
699     parse_hello (cfg, put_uri);
700     put_uri = NULL;
701     return;
702   }
703   if (GNUNET_YES == get_info)
704   {
705     get_info = GNUNET_NO;
706     GPI_plugins_load (cfg);
707     GNUNET_PEERINFO_iterate (peerinfo, NULL,
708                              GNUNET_TIME_relative_multiply
709                              (GNUNET_TIME_UNIT_SECONDS, 5), &print_peer_info,
710                              NULL);
711     return;
712   }
713   if (GNUNET_YES == get_self)
714   {
715     struct GNUNET_CRYPTO_HashAsciiEncoded enc;
716
717     get_self = GNUNET_NO;
718     GNUNET_CRYPTO_hash_to_enc (&my_peer_identity.hashPubKey, &enc);
719     if (be_quiet)
720       printf ("%s\n", (char *) &enc);
721     else
722       printf (_("I am peer `%s'.\n"), (const char *) &enc);
723   }
724   if (GNUNET_YES == get_uri)
725   {
726     struct PrintContext *pc;
727     char *pkey;
728     ssize_t l;
729     ssize_t pl;
730
731     // FIXME...
732     pc = GNUNET_malloc (sizeof (struct PrintContext));
733     pkey = GNUNET_CRYPTO_rsa_public_key_to_string (&my_public_key);
734     pl = strlen ("gnunet://hello/");
735     l = strlen (pkey) + pl;
736     pc->uri = GNUNET_malloc (l + 1);
737     strcpy (pc->uri, "gnunet://hello/");
738     strcpy (&pc->uri[pl], pkey);
739     pc->uri_len = l;
740
741     GPI_plugins_load (cfg);
742     pic = GNUNET_PEERINFO_iterate (peerinfo, &my_peer_identity,
743                                    GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5),
744                                    &print_my_uri, pc);
745     return;
746   }
747   GNUNET_SCHEDULER_shutdown ();
748 }
749
750
751 /**
752  * The main function to obtain peer information.
753  *
754  * @param argc number of arguments from the command line
755  * @param argv command line arguments
756  * @return 0 ok, 1 on error
757  */
758 int
759 main (int argc, char *const *argv)
760 {
761   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
762     {'n', "numeric", NULL,
763      gettext_noop ("don't resolve host names"),
764      0, &GNUNET_GETOPT_set_one, &no_resolve},
765     {'q', "quiet", NULL,
766      gettext_noop ("output only the identity strings"),
767      0, &GNUNET_GETOPT_set_one, &be_quiet},
768     {'s', "self", NULL,
769      gettext_noop ("output our own identity only"),
770      0, &GNUNET_GETOPT_set_one, &get_self},
771     {'i', "info", NULL,
772      gettext_noop ("list all known peers"),
773      0, &GNUNET_GETOPT_set_one, &get_info},
774     {'g', "get-hello", NULL,
775      gettext_noop ("also output HELLO uri(s)"),
776      0, &GNUNET_GETOPT_set_one, &get_uri},
777     {'p', "put-hello", "HELLO",
778      gettext_noop ("add given HELLO uri to the database"),
779      1, &GNUNET_GETOPT_set_string, &put_uri},
780     GNUNET_GETOPT_OPTION_END
781   };
782   return (GNUNET_OK ==
783           GNUNET_PROGRAM_run (argc, argv, "gnunet-peerinfo",
784                               gettext_noop ("Print information about peers."),
785                               options, &run, NULL)) ? 0 : 1;
786 }
787
788 /* end of gnunet-peerinfo.c */