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