LRN: updates to pseudonym API from #1952, change pseudonym management
[oweals/gnunet.git] / src / chat / gnunet-chat.c
1 /*
2      This file is part of GNUnet.
3      (C) 2007, 2008, 2011 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 chat/gnunet-chat.c
23  * @brief Minimal chat command line tool
24  * @author Christian Grothoff
25  * @author Nathan Evans
26  * @author Vitaly Minko
27  */
28
29 #include "platform.h"
30 #include "gnunet_getopt_lib.h"
31 #include "gnunet_program_lib.h"
32 #include "gnunet_chat_service.h"
33 #include <fcntl.h>
34
35 static int ret;
36
37 static const struct GNUNET_CONFIGURATION_Handle *cfg;
38
39 static char *nickname;
40
41 static char *room_name;
42
43 static struct GNUNET_CONTAINER_MetaData *meta;
44
45 static struct GNUNET_CHAT_Room *room;
46
47 static GNUNET_SCHEDULER_TaskIdentifier handle_cmd_task =
48     GNUNET_SCHEDULER_NO_TASK;
49
50 struct ChatCommand
51 {
52   const char *command;
53   int (*Action) (const char *arguments, const void *xtra);
54   const char *helptext;
55 };
56
57 struct UserList
58 {
59   struct UserList *next;
60   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pkey;
61   int ignored;
62 };
63
64 static struct UserList *users;
65
66 static void
67 free_user_list ()
68 {
69   struct UserList *next;
70
71   while (NULL != users)
72   {
73     next = users->next;
74     GNUNET_free (users);
75     users = next;
76   }
77 }
78
79 static int
80 do_help (const char *args, const void *xtra);
81
82
83 /**
84  * Callback used for notification that we have joined the room.
85  *
86  * @param cls closure
87  * @return GNUNET_OK
88  */
89 static int
90 join_cb (void *cls)
91 {
92   FPRINTF (stdout, "%s",  _("Joined\n"));
93   return GNUNET_OK;
94 }
95
96
97 /**
98  * Callback used for notification about incoming messages.
99  *
100  * @param cls closure, NULL
101  * @param room in which room was the message received?
102  * @param sender what is the ID of the sender? (maybe NULL)
103  * @param member_info information about the joining member
104  * @param message the message text
105  * @param timestamp time when the member joined
106  * @param options options for the message
107  * @return GNUNET_OK to accept the message now, GNUNET_NO to
108  *         accept (but user is away), GNUNET_SYSERR to signal denied delivery
109  */
110 static int
111 receive_cb (void *cls, struct GNUNET_CHAT_Room *room,
112             const GNUNET_HashCode * sender,
113             const struct GNUNET_CONTAINER_MetaData *member_info,
114             const char *message, struct GNUNET_TIME_Absolute timestamp,
115             enum GNUNET_CHAT_MsgOptions options)
116 {
117   char *non_unique_nick;
118   char *nick;
119   int nick_is_a_dup;
120   char *time;
121   const char *fmt;
122
123   if (NULL == sender)
124     nick = GNUNET_strdup (_("anonymous"));
125   else
126   {
127     if (GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
128         sender, NULL, NULL, &non_unique_nick, &nick_is_a_dup)
129         || (nick_is_a_dup == GNUNET_YES))
130     {
131       GNUNET_free (non_unique_nick);
132       non_unique_nick = GNUNET_strdup (_("anonymous"));
133     }
134     nick = GNUNET_PSEUDONYM_name_uniquify (cfg, sender, non_unique_nick, NULL);
135     GNUNET_free (non_unique_nick);
136   }
137
138   fmt = NULL;
139   switch ((int) options)
140   {
141   case GNUNET_CHAT_MSG_OPTION_NONE:
142   case GNUNET_CHAT_MSG_ANONYMOUS:
143     fmt = _("(%s) `%s' said: %s\n");
144     break;
145   case GNUNET_CHAT_MSG_PRIVATE:
146     fmt = _("(%s) `%s' said to you: %s\n");
147     break;
148   case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ANONYMOUS:
149     fmt = _("(%s) `%s' said to you: %s\n");
150     break;
151   case GNUNET_CHAT_MSG_AUTHENTICATED:
152     fmt = _("(%s) `%s' said for sure: %s\n");
153     break;
154   case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_AUTHENTICATED:
155     fmt = _("(%s) `%s' said to you for sure: %s\n");
156     break;
157   case GNUNET_CHAT_MSG_ACKNOWLEDGED:
158     fmt = _("(%s) `%s' was confirmed that you received: %s\n");
159     break;
160   case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED:
161     fmt = _("(%s) `%s' was confirmed that you and only you received: %s\n");
162     break;
163   case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_ACKNOWLEDGED:
164     fmt = _("(%s) `%s' was confirmed that you received from him or her: %s\n");
165     break;
166   case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED:
167     fmt =
168         _
169         ("(%s) `%s' was confirmed that you and only you received from him or her: %s\n");
170     break;
171   case GNUNET_CHAT_MSG_OFF_THE_RECORD:
172     fmt = _("(%s) `%s' said off the record: %s\n");
173     break;
174   default:
175     fmt = _("(%s) <%s> said using an unknown message type: %s\n");
176     break;
177   }
178   time = GNUNET_STRINGS_absolute_time_to_string (timestamp);
179   FPRINTF (stdout, fmt, time, nick, message);
180   GNUNET_free (nick);
181   GNUNET_free (time);
182   return GNUNET_OK;
183 }
184
185
186 /**
187  * Callback used for message delivery confirmations.
188  *
189  * @param cls closure, NULL
190  * @param room in which room was the message received?
191  * @param orig_seq_number sequence number of the original message
192  * @param timestamp when was the message received?
193  * @param receiver who is confirming the receipt?
194  * @return GNUNET_OK to continue, GNUNET_SYSERR to refuse processing further
195  *         confirmations from anyone for this message
196  */
197 static int
198 confirmation_cb (void *cls, struct GNUNET_CHAT_Room *room,
199                  uint32_t orig_seq_number,
200                  struct GNUNET_TIME_Absolute timestamp,
201                  const GNUNET_HashCode * receiver)
202 {
203   char *nick;
204   char *unique_nick;
205   int nick_is_a_dup;
206
207   if (GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
208       receiver, NULL, NULL, &nick, &nick_is_a_dup)
209       || (nick_is_a_dup == GNUNET_YES))
210   {
211     GNUNET_free (nick);
212     nick = GNUNET_strdup (_("anonymous"));
213   }
214   unique_nick = GNUNET_PSEUDONYM_name_uniquify (cfg, receiver, nick, NULL);
215   GNUNET_free (nick);
216   FPRINTF (stdout, _("'%s' acknowledged message #%d\n"), unique_nick, orig_seq_number);
217   GNUNET_free (unique_nick);
218   return GNUNET_OK;
219 }
220
221
222 /**
223  * Callback used for notification that another room member has joined or left.
224  *
225  * @param cls closure (not used)
226  * @param member_info will be non-null if the member is joining, NULL if he is
227  *        leaving
228  * @param member_id hash of public key of the user (for unique identification)
229  * @param options what types of messages is this member willing to receive?
230  * @return GNUNET_OK
231  */
232 static int
233 member_list_cb (void *cls, const struct GNUNET_CONTAINER_MetaData *member_info,
234                 const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *member_id,
235                 enum GNUNET_CHAT_MsgOptions options)
236 {
237   char *nick, *non_unique_nick;
238   int nick_is_a_dup;
239   GNUNET_HashCode id;
240   struct UserList *pos;
241   struct UserList *prev;
242
243   GNUNET_CRYPTO_hash (member_id,
244                       sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
245                       &id);
246   if (GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
247       &id, NULL, NULL, &non_unique_nick, &nick_is_a_dup)
248       || (nick_is_a_dup == GNUNET_YES))
249   {
250     GNUNET_free (non_unique_nick);
251     non_unique_nick = GNUNET_strdup (_("anonymous"));
252   }
253   nick = GNUNET_PSEUDONYM_name_uniquify (cfg, &id, non_unique_nick, NULL);
254   GNUNET_free (non_unique_nick);
255
256   FPRINTF (stdout,
257            member_info !=
258            NULL ? _("`%s' entered the room\n") : _("`%s' left the room\n"),
259            nick);
260   GNUNET_free (nick);
261   if (NULL != member_info)
262   {
263     /* user joining */
264     pos = GNUNET_malloc (sizeof (struct UserList));
265     pos->next = users;
266     pos->pkey = *member_id;
267     pos->ignored = GNUNET_NO;
268     users = pos;
269   }
270   else
271   {
272     /* user leaving */
273     prev = NULL;
274     pos = users;
275     while ((NULL != pos) &&
276            (0 !=
277             memcmp (&pos->pkey, member_id,
278                     sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded))))
279     {
280       prev = pos;
281       pos = pos->next;
282     }
283     if (NULL == pos)
284     {
285       GNUNET_break (0);
286     }
287     else
288     {
289       if (NULL == prev)
290         users = pos->next;
291       else
292         prev->next = pos->next;
293       GNUNET_free (pos);
294     }
295   }
296   return GNUNET_OK;
297 }
298
299
300 static int
301 do_join (const char *arg, const void *xtra)
302 {
303   char *my_name;
304   int my_name_is_a_dup;
305   GNUNET_HashCode me;
306
307   if (arg[0] == '#')
308     arg++;                      /* ignore first hash */
309   GNUNET_CHAT_leave_room (room);
310   free_user_list ();
311   GNUNET_free (room_name);
312   room_name = GNUNET_strdup (arg);
313   room =
314       GNUNET_CHAT_join_room (cfg, nickname, meta, room_name, -1, &join_cb, NULL,
315                              &receive_cb, NULL, &member_list_cb, NULL,
316                              &confirmation_cb, NULL, &me);
317   if (NULL == room)
318   {
319     FPRINTF (stdout, "%s",  _("Could not change username\n"));
320     return GNUNET_SYSERR;
321   }
322   if ((GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
323       &me, NULL, NULL, &my_name, &my_name_is_a_dup)) ||
324       (my_name_is_a_dup == GNUNET_YES))
325   {
326     GNUNET_free (my_name);
327     my_name = GNUNET_strdup (_("anonymous"));
328   }
329   /* Don't uniquify our own name - other people will have a different
330    * suffix for our own name anyway.
331    */
332   FPRINTF (stdout, _("Joining room `%s' as user `%s'...\n"), room_name,
333            my_name);
334   GNUNET_free (my_name);
335   return GNUNET_OK;
336 }
337
338
339 static int
340 do_nick (const char *msg, const void *xtra)
341 {
342   char *my_name;
343   int my_name_is_a_dup;
344   GNUNET_HashCode me;
345
346   GNUNET_CHAT_leave_room (room);
347   free_user_list ();
348   GNUNET_free (nickname);
349   GNUNET_CONTAINER_meta_data_destroy (meta);
350   nickname = GNUNET_strdup (msg);
351   meta = GNUNET_CONTAINER_meta_data_create ();
352   GNUNET_CONTAINER_meta_data_insert (meta, "<gnunet>", EXTRACTOR_METATYPE_TITLE,
353                                      EXTRACTOR_METAFORMAT_UTF8, "text/plain",
354                                      nickname, strlen (nickname) + 1);
355   room =
356       GNUNET_CHAT_join_room (cfg, nickname, meta, room_name, -1, &join_cb, NULL,
357                              &receive_cb, NULL, &member_list_cb, NULL,
358                              &confirmation_cb, NULL, &me);
359   if (NULL == room)
360   {
361     FPRINTF (stdout, "%s",  _("Could not change username\n"));
362     return GNUNET_SYSERR;
363   }
364   if ((GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
365       &me, NULL, NULL, &my_name, &my_name_is_a_dup)) ||
366       (my_name_is_a_dup == GNUNET_YES))
367   {
368     GNUNET_free (my_name);
369     my_name = GNUNET_strdup (_("anonymous"));
370   }
371   FPRINTF (stdout, _("Changed username to `%s'\n"), my_name);
372   GNUNET_free (my_name);
373   return GNUNET_OK;
374 }
375
376
377 static int
378 do_names (const char *msg, const void *xtra)
379 {
380   char *name;
381   char *unique_name;
382   int name_is_a_dup;
383   struct UserList *pos;
384   GNUNET_HashCode pid;
385
386   FPRINTF (stdout, _("Users in room `%s': "), room_name);
387   pos = users;
388   while (NULL != pos)
389   {
390     GNUNET_CRYPTO_hash (&pos->pkey,
391                         sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
392                         &pid);
393     if (GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
394         &pid, NULL, NULL, &name, &name_is_a_dup)
395         || (name_is_a_dup == GNUNET_YES))
396     {
397       GNUNET_free (name);
398       name = GNUNET_strdup (_("anonymous"));
399     }
400     unique_name = GNUNET_PSEUDONYM_name_uniquify (cfg, &pid, name, NULL);
401     GNUNET_free (name);
402     FPRINTF (stdout, "`%s' ", unique_name);
403     GNUNET_free (unique_name);
404     pos = pos->next;
405   }
406   FPRINTF (stdout, "%s",  "\n");
407   return GNUNET_OK;
408 }
409
410
411 static int
412 do_send (const char *msg, const void *xtra)
413 {
414   uint32_t seq;
415
416   GNUNET_CHAT_send_message (room, msg, GNUNET_CHAT_MSG_OPTION_NONE, NULL, &seq);
417   return GNUNET_OK;
418 }
419
420
421 static int
422 do_send_pm (const char *msg, const void *xtra)
423 {
424   char *user;
425   GNUNET_HashCode uid;
426   GNUNET_HashCode pid;
427   uint32_t seq;
428   struct UserList *pos;
429
430   if (NULL == strstr (msg, " "))
431   {
432     FPRINTF (stderr, "%s",  _("Syntax: /msg USERNAME MESSAGE"));
433     return GNUNET_OK;
434   }
435   user = GNUNET_strdup (msg);
436   strstr (user, " ")[0] = '\0';
437   msg += strlen (user) + 1;
438   if (GNUNET_OK != GNUNET_PSEUDONYM_name_to_id (cfg, user, &uid))
439   {
440     FPRINTF (stderr,
441         _("Unknown user `%s'. Make sure you specify its numeric suffix, if any.\n"),
442         user);
443     GNUNET_free (user);
444     return GNUNET_OK;
445   }
446   pos = users;
447   while (NULL != pos)
448   {
449     GNUNET_CRYPTO_hash (&pos->pkey,
450                         sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
451                         &pid);
452     if (0 == memcmp (&pid, &uid, sizeof (GNUNET_HashCode)))
453       break;
454     pos = pos->next;
455   }
456   if (NULL == pos)
457   {
458     FPRINTF (stderr, _("User `%s' is currently not in the room!\n"), user);
459     GNUNET_free (user);
460     return GNUNET_OK;
461   }
462   GNUNET_CHAT_send_message (room, msg, GNUNET_CHAT_MSG_PRIVATE, &pos->pkey,
463                             &seq);
464   GNUNET_free (user);
465   return GNUNET_OK;
466 }
467
468
469 static int
470 do_send_sig (const char *msg, const void *xtra)
471 {
472   uint32_t seq;
473
474   GNUNET_CHAT_send_message (room, msg, GNUNET_CHAT_MSG_AUTHENTICATED, NULL,
475                             &seq);
476   return GNUNET_OK;
477 }
478
479
480 static int
481 do_send_ack (const char *msg, const void *xtra)
482 {
483   uint32_t seq;
484
485   GNUNET_CHAT_send_message (room, msg, GNUNET_CHAT_MSG_ACKNOWLEDGED, NULL,
486                             &seq);
487   return GNUNET_OK;
488 }
489
490
491 static int
492 do_send_anonymous (const char *msg, const void *xtra)
493 {
494   uint32_t seq;
495
496   GNUNET_CHAT_send_message (room, msg, GNUNET_CHAT_MSG_ANONYMOUS, NULL, &seq);
497   return GNUNET_OK;
498 }
499
500
501 static int
502 do_quit (const char *args, const void *xtra)
503 {
504   return GNUNET_SYSERR;
505 }
506
507
508 static int
509 do_unknown (const char *msg, const void *xtra)
510 {
511   FPRINTF (stderr, _("Unknown command `%s'\n"), msg);
512   return GNUNET_OK;
513 }
514
515
516 /**
517  * List of supported IRC commands. The order matters!
518  */
519 static struct ChatCommand commands[] = {
520   {"/join ", &do_join,
521    gettext_noop
522    ("Use `/join #roomname' to join a chat room. Joining a room will cause you"
523     " to leave the current room")},
524   {"/nick ", &do_nick,
525    gettext_noop
526    ("Use `/nick nickname' to change your nickname.  This will cause you to"
527     " leave the current room and immediately rejoin it with the new name.")},
528   {"/msg ", &do_send_pm,
529    gettext_noop
530    ("Use `/msg nickname message' to send a private message to the specified"
531     " user")},
532   {"/notice ", &do_send_pm,
533    gettext_noop ("The `/notice' command is an alias for `/msg'")},
534   {"/query ", &do_send_pm,
535    gettext_noop ("The `/query' command is an alias for `/msg'")},
536   {"/sig ", &do_send_sig,
537    gettext_noop ("Use `/sig message' to send a signed public message")},
538   {"/ack ", &do_send_ack,
539    gettext_noop
540    ("Use `/ack message' to require signed acknowledgment of the message")},
541   {"/anonymous ", &do_send_anonymous,
542    gettext_noop
543    ("Use `/anonymous message' to send a public anonymous message")},
544   {"/anon ", &do_send_anonymous,
545    gettext_noop ("The `/anon' command is an alias for `/anonymous'")},
546   {"/quit", &do_quit,
547    gettext_noop ("Use `/quit' to terminate gnunet-chat")},
548   {"/leave", &do_quit,
549    gettext_noop ("The `/leave' command is an alias for `/quit'")},
550   {"/names", &do_names,
551    gettext_noop
552    ("Use `/names' to list all of the current members in the chat room")},
553   {"/help", &do_help,
554    gettext_noop ("Use `/help command' to get help for a specific command")},
555   /* Add standard commands:
556    * /whois (print metadata),
557    * /ignore (set flag, check on receive!) */
558   /* the following three commands must be last! */
559   {"/", &do_unknown, NULL},
560   {"", &do_send, NULL},
561   {NULL, NULL, NULL},
562 };
563
564
565 static int
566 do_help (const char *args, const void *xtra)
567 {
568   int i;
569
570   i = 0;
571   while ((NULL != args) && (0 != strlen (args)) &&
572          (commands[i].Action != &do_help))
573   {
574     if (0 == strncasecmp (&args[1], &commands[i].command[1], strlen (args) - 1))
575     {
576       FPRINTF (stdout, "%s\n", gettext (commands[i].helptext));
577       return GNUNET_OK;
578     }
579     i++;
580   }
581   i = 0;
582   FPRINTF (stdout, "%s",  "Available commands:");
583   while (commands[i].Action != &do_help)
584   {
585     FPRINTF (stdout, " %s", gettext (commands[i].command));
586     i++;
587   }
588   FPRINTF (stdout, "%s",  "\n");
589   FPRINTF (stdout, "%s\n", gettext (commands[i].helptext));
590   return GNUNET_OK;
591 }
592
593
594 static void
595 do_stop_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
596 {
597   GNUNET_CHAT_leave_room (room);
598   if (handle_cmd_task != GNUNET_SCHEDULER_NO_TASK)
599   {
600     GNUNET_SCHEDULER_cancel (handle_cmd_task);
601     handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
602   }
603   free_user_list ();
604   GNUNET_CONTAINER_meta_data_destroy (meta);
605   GNUNET_free (room_name);
606   GNUNET_free (nickname);
607 }
608
609
610 void
611 handle_command (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
612 {
613   char message[MAX_MESSAGE_LENGTH + 1];
614   int i;
615
616   /* read message from command line and handle it */
617   memset (message, 0, MAX_MESSAGE_LENGTH + 1);
618   if (NULL == fgets (message, MAX_MESSAGE_LENGTH, stdin))
619     goto next;
620   if (strlen (message) == 0)
621     goto next;
622   if (message[strlen (message) - 1] == '\n')
623     message[strlen (message) - 1] = '\0';
624   if (strlen (message) == 0)
625     goto next;
626   i = 0;
627   while ((NULL != commands[i].command) &&
628          (0 !=
629           strncasecmp (commands[i].command, message,
630                        strlen (commands[i].command))))
631     i++;
632   if (GNUNET_OK !=
633       commands[i].Action (&message[strlen (commands[i].command)], NULL))
634     goto out;
635
636 next:
637   handle_cmd_task =
638     GNUNET_SCHEDULER_add_delayed_with_priority (GNUNET_TIME_relative_multiply
639                                                 (GNUNET_TIME_UNIT_MILLISECONDS, 100),
640                                                 GNUNET_SCHEDULER_PRIORITY_UI,
641                                                 &handle_command, NULL);
642   return;
643
644 out:
645   handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
646   GNUNET_SCHEDULER_shutdown ();
647 }
648
649
650 /**
651  * Main function that will be run by the scheduler.
652  *
653  * @param cls closure, NULL
654  * @param args remaining command-line arguments
655  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
656  * @param c configuration
657  */
658 static void
659 run (void *cls, char *const *args, const char *cfgfile,
660      const struct GNUNET_CONFIGURATION_Handle *c)
661 {
662   GNUNET_HashCode me;
663   char *my_name;
664   int my_name_is_a_dup;
665
666   cfg = c;
667   /* check arguments */
668   if (NULL == nickname)
669   {
670     FPRINTF (stderr, "%s",  _("You must specify a nickname\n"));
671     ret = -1;
672     return;
673   }
674   if (NULL == room_name)
675     room_name = GNUNET_strdup ("gnunet");
676   meta = GNUNET_CONTAINER_meta_data_create ();
677   GNUNET_CONTAINER_meta_data_insert (meta, "<gnunet>", EXTRACTOR_METATYPE_TITLE,
678                                      EXTRACTOR_METAFORMAT_UTF8, "text/plain",
679                                      nickname, strlen (nickname) + 1);
680   room =
681       GNUNET_CHAT_join_room (cfg, nickname, meta, room_name, -1, &join_cb, NULL,
682                              &receive_cb, NULL, &member_list_cb, NULL,
683                              &confirmation_cb, NULL, &me);
684   if (NULL == room)
685   {
686     FPRINTF (stderr, _("Failed to join room `%s'\n"), room_name);
687     GNUNET_free (room_name);
688     GNUNET_free (nickname);
689     GNUNET_CONTAINER_meta_data_destroy (meta);
690     ret = -1;
691     return;
692   }
693   if ((GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
694       &me, NULL, NULL, &my_name, &my_name_is_a_dup)) ||
695       (my_name_is_a_dup == GNUNET_YES))
696   {
697     GNUNET_free (my_name);
698     my_name = GNUNET_strdup (_("anonymous"));
699   }
700   FPRINTF (stdout, _("Joining room `%s' as user `%s'...\n"), room_name,
701            my_name);
702   GNUNET_free (my_name);
703   handle_cmd_task =
704       GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_UI,
705                                           &handle_command, NULL);
706   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &do_stop_task,
707                                 NULL);
708 }
709
710
711 /**
712  * The main function to chat via GNUnet.
713  *
714  * @param argc number of arguments from the command line
715  * @param argv command line arguments
716  * @return 0 ok, 1 on error
717  */
718 int
719 main (int argc, char *const *argv)
720 {
721   int flags;
722
723   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
724     {'n', "nick", "NAME",
725      gettext_noop ("set the nickname to use (required)"),
726      1, &GNUNET_GETOPT_set_string, &nickname},
727     {'r', "room", "NAME",
728      gettext_noop ("set the chat room to join"),
729      1, &GNUNET_GETOPT_set_string, &room_name},
730     GNUNET_GETOPT_OPTION_END
731   };
732
733 #ifndef WINDOWS
734   flags = fcntl (0, F_GETFL, 0);
735   flags |= O_NONBLOCK;
736   fcntl (0, F_SETFL, flags);
737 #endif
738   return (GNUNET_OK ==
739           GNUNET_PROGRAM_run (argc, argv, "gnunet-chat",
740                               gettext_noop ("Join a chat on GNUnet."), options,
741                               &run, NULL)) ? ret : 1;
742 }
743
744 /* end of gnunet-chat.c */