Mantis #1644, unmodified, patch by vminko
[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 member_info 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 member_info will be non-null if the member is joining, NULL if he is
187  *        leaving
188  * @param member_id hash of public key of the user (for unique identification)
189  * @param options what types of messages is this member willing to receive?
190  * @return GNUNET_OK
191  */
192 static int
193 member_list_cb (void *cls,
194                 const struct GNUNET_CONTAINER_MetaData *member_info,
195                 const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *member_id,
196                 enum GNUNET_CHAT_MsgOptions options)
197 {
198   char *nick;
199   GNUNET_HashCode id;
200   struct UserList *pos;
201   struct UserList *prev;
202
203   GNUNET_CRYPTO_hash (member_id,
204                       sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
205                       &id);
206   nick = GNUNET_PSEUDONYM_id_to_name (cfg, &id);
207   fprintf (stdout, member_info != NULL
208            ? _("`%s' entered the room\n") : _("`%s' left the room\n"), nick);
209   GNUNET_free (nick);
210   if (NULL != member_info)
211     {
212       /* user joining */
213       pos = GNUNET_malloc (sizeof (struct UserList));
214       pos->next = users;
215       pos->pkey = *member_id;
216       pos->ignored = GNUNET_NO;
217       users = pos;
218     }
219   else
220     {
221       /* user leaving */
222       prev = NULL;
223       pos = users;
224       while ((NULL != pos) &&
225              (0 != memcmp (&pos->pkey,
226                            member_id,
227                            sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded))))
228         {
229           prev = pos;
230           pos = pos->next;
231         }
232       if (NULL == pos)
233         {
234           GNUNET_break (0);
235         }
236       else
237         {
238           if (NULL == prev)
239             users = pos->next;
240           else
241             prev->next = pos->next;
242           GNUNET_free (pos);
243         }
244     }
245   return GNUNET_OK;
246 }
247
248
249 static int
250 do_transmit (const char *msg, const void *xtra)
251 {
252   uint32_t seq;
253   GNUNET_CHAT_send_message (room,
254                             msg,
255                             GNUNET_CHAT_MSG_OPTION_NONE,
256                             NULL, &seq);
257   return GNUNET_OK;
258 }
259
260
261 static int
262 do_join (const char *arg, const void *xtra)
263 {
264   char *my_name;
265   GNUNET_HashCode me;
266
267   if (arg[0] == '#')
268     arg++;                      /* ignore first hash */
269   GNUNET_CHAT_leave_room (room);
270   free_user_list ();
271   GNUNET_free (room_name);
272   room_name = GNUNET_strdup (arg);
273   room = GNUNET_CHAT_join_room (cfg,
274                                 nickname,
275                                 meta,
276                                 room_name,
277                                 -1,
278                                 &receive_cb, NULL,
279                                 &member_list_cb, NULL,
280                                 &confirmation_cb, NULL, &me);
281   if (NULL == room)
282     {
283       fprintf (stdout, _("Could not change username\n"));
284       return GNUNET_SYSERR;
285     }
286   my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
287   fprintf (stdout, _("Joined room `%s' as user `%s'\n"), room_name, my_name);
288   GNUNET_free (my_name);
289   return GNUNET_OK;
290 }
291
292
293 static int
294 do_nick (const char *msg, const void *xtra)
295 {
296   char *my_name;
297   GNUNET_HashCode me;
298
299   GNUNET_CHAT_leave_room (room);
300   free_user_list ();
301   GNUNET_free (nickname);
302   GNUNET_CONTAINER_meta_data_destroy (meta);
303   nickname = GNUNET_strdup (msg);
304   meta = GNUNET_CONTAINER_meta_data_create ();
305   GNUNET_CONTAINER_meta_data_insert (meta,
306                                      "<gnunet>",
307                                      EXTRACTOR_METATYPE_TITLE,
308                                      EXTRACTOR_METAFORMAT_UTF8,
309                                      "text/plain",
310                                      nickname,
311                                      strlen(nickname)+1);
312   room = GNUNET_CHAT_join_room (cfg,
313                                 nickname,
314                                 meta,
315                                 room_name,
316                                 -1,
317                                 &receive_cb, NULL,
318                                 &member_list_cb, NULL,
319                                 &confirmation_cb, NULL, &me);
320   if (NULL == room)
321     {
322       fprintf (stdout, _("Could not change username\n"));
323       return GNUNET_SYSERR;
324     }
325   my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
326   fprintf (stdout, _("Changed username to `%s'\n"), my_name);
327   GNUNET_free (my_name);
328   return GNUNET_OK;
329 }
330
331
332 static int
333 do_names (const char *msg, const void *xtra)
334 {
335   char *name;
336   struct UserList *pos;
337   GNUNET_HashCode pid;
338
339   fprintf (stdout, _("Users in room `%s': "), room_name);
340   pos = users;
341   while (NULL != pos)
342     {
343       GNUNET_CRYPTO_hash (&pos->pkey,
344                           sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
345                           &pid);
346       name = GNUNET_PSEUDONYM_id_to_name (cfg, &pid);
347       fprintf (stdout, "`%s' ", name);
348       GNUNET_free (name);
349       pos = pos->next;
350     }
351   fprintf (stdout, "\n");
352   return GNUNET_OK;
353 }
354
355
356 static int
357 do_pm (const char *msg, const void *xtra)
358 {
359   char *user;
360   GNUNET_HashCode uid;
361   GNUNET_HashCode pid;
362   uint32_t seq;
363   struct UserList *pos;
364
365   if (NULL == strstr (msg, " "))
366     {
367       fprintf (stderr, _("Syntax: /msg USERNAME MESSAGE"));
368       return GNUNET_OK;
369     }
370   user = GNUNET_strdup (msg);
371   strstr (user, " ")[0] = '\0';
372   msg += strlen (user) + 1;
373   if (GNUNET_OK != GNUNET_PSEUDONYM_name_to_id (cfg, user, &uid))
374     {
375       fprintf (stderr, _("Unknown user `%s'\n"), user);
376       GNUNET_free (user);
377       return GNUNET_OK;
378     }
379   pos = users;
380   while (NULL != pos)
381     {
382       GNUNET_CRYPTO_hash (&pos->pkey,
383                           sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
384                           &pid);
385       if (0 == memcmp (&pid, &uid, sizeof (GNUNET_HashCode)))
386         break;
387       pos = pos->next;
388     }
389   if (NULL == pos)
390     {
391       fprintf (stderr, _("User `%s' is currently not in the room!\n"), user);
392       GNUNET_free (user);
393       return GNUNET_OK;
394     }
395   GNUNET_CHAT_send_message (room,
396                             msg,
397                             GNUNET_CHAT_MSG_PRIVATE,
398                             &pos->pkey,
399                             &seq);
400   GNUNET_free (user);
401   return GNUNET_OK;
402 }
403
404
405 static int
406 do_transmit_sig (const char *msg, const void *xtra)
407 {
408   uint32_t seq;
409   GNUNET_CHAT_send_message (room,
410                             msg,
411                             GNUNET_CHAT_MSG_AUTHENTICATED,
412                             NULL, &seq);
413   return GNUNET_OK;
414 }
415
416
417 static int
418 do_transmit_ack (const char *msg, const void *xtra)
419 {
420   uint32_t seq;
421   GNUNET_CHAT_send_message (room,
422                             msg,
423                             GNUNET_CHAT_MSG_ACKNOWLEDGED,
424                             NULL, &seq);
425   return GNUNET_OK;
426 }
427
428
429 static int
430 do_quit (const char *args, const void *xtra)
431 {
432   return GNUNET_SYSERR;
433 }
434
435
436 static int
437 do_unknown (const char *msg, const void *xtra)
438 {
439   fprintf (stderr, _("Unknown command `%s'\n"), msg);
440   return GNUNET_OK;
441 }
442
443
444 /**
445  * List of supported IRC commands. The order matters!
446  */
447 static struct ChatCommand commands[] = {
448   {"/join ", &do_join,
449    gettext_noop
450    ("Use `/join #roomname' to join a chat room. Joining a room will cause you"
451     " to leave the current room")},
452   {"/nick ", &do_nick,
453    gettext_noop
454    ("Use `/nick nickname' to change your nickname.  This will cause you to"
455     " leave the current room and immediately rejoin it with the new name.")},
456   {"/msg ", &do_pm,
457    gettext_noop
458    ("Use `/msg nickname message' to send a private message to the specified"
459     " user")},
460   {"/notice ", &do_pm,
461    gettext_noop ("The `/notice' command is an alias for `/msg'")},
462   {"/query ", &do_pm,
463    gettext_noop ("The `/query' command is an alias for `/msg'")},
464   {"/sig ", &do_transmit_sig,
465    gettext_noop ("Use `/sig message' to send a signed public message")},
466   {"/ack ", &do_transmit_ack,
467    gettext_noop
468    ("Use `/ack message' to require signed acknowledgment of the message")},
469   {"/quit", &do_quit,
470    gettext_noop ("Use `/quit' to terminate gnunet-chat")},
471   {"/leave", &do_quit,
472    gettext_noop ("The `/leave' command is an alias for `/quit'")},
473   {"/names", &do_names,
474    gettext_noop
475    ("Use `/names' to list all of the current members in the chat room")},
476   {"/help", &do_help,
477    gettext_noop ("Use `/help command' to get help for a specific command")},
478   /* Add standard commands:
479      /whois (print metadata),
480      /ignore (set flag, check on receive!) */
481   /* Add special commands (currently supported):
482      + anonymous msgs
483      + authenticated msgs
484    */
485   /* the following three commands must be last! */
486   {"/", &do_unknown, NULL},
487   {"", &do_transmit, NULL},
488   {NULL, NULL, NULL},
489 };
490
491
492 static int
493 do_help (const char *args, const void *xtra)
494 {
495   int i;
496   i = 0;
497   while ((NULL != args) &&
498          (0 != strlen (args)) && (commands[i].Action != &do_help))
499     {
500       if (0 ==
501           strncasecmp (&args[1], &commands[i].command[1], strlen (args) - 1))
502         {
503           fprintf (stdout, "%s\n", gettext (commands[i].helptext));
504           return GNUNET_OK;
505         }
506       i++;
507     }
508   i = 0;
509   fprintf (stdout, "Available commands:");
510   while (commands[i].Action != &do_help)
511     {
512       fprintf (stdout, " %s", gettext (commands[i].command));
513       i++;
514     }
515   fprintf (stdout, "\n");
516   fprintf (stdout, "%s\n", gettext (commands[i].helptext));
517   return GNUNET_OK;
518 }
519
520
521 static void 
522 do_stop_task (void *cls,
523               const struct GNUNET_SCHEDULER_TaskContext *tc)
524 {
525   GNUNET_CHAT_leave_room (room);
526   if (handle_cmd_task != GNUNET_SCHEDULER_NO_TASK)
527     {
528       GNUNET_SCHEDULER_cancel (handle_cmd_task);
529       handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
530     }     
531   free_user_list ();
532   GNUNET_CONTAINER_meta_data_destroy (meta);
533   GNUNET_free (room_name);
534   GNUNET_free (nickname);
535 }
536
537
538 void
539 handle_command (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
540 {
541   char message[MAX_MESSAGE_LENGTH + 1];
542   int i;
543
544   /* read message from command line and handle it */
545   memset (message, 0, MAX_MESSAGE_LENGTH + 1);
546   if (NULL == fgets (message, MAX_MESSAGE_LENGTH, stdin))
547     goto next;
548   if (strlen (message) == 0)
549     goto next;
550   if (message[strlen (message) - 1] == '\n')
551     message[strlen (message) - 1] = '\0';
552   if (strlen (message) == 0)
553     goto next;
554   i = 0;
555   while ((NULL != commands[i].command) &&
556          (0 != strncasecmp (commands[i].command,
557                             message, strlen (commands[i].command))))
558     i++;
559   if (GNUNET_OK !=
560       commands[i].Action (&message[strlen (commands[i].command)], NULL))
561     goto out;
562
563 next:
564   handle_cmd_task =
565     GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
566                                                                  100),
567                                   &handle_command,
568                                   NULL);
569   return;
570
571 out:
572   handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
573   GNUNET_SCHEDULER_shutdown ();
574 }
575
576
577 /**
578  * Main function that will be run by the scheduler.
579  *
580  * @param cls closure, NULL
581  * @param args remaining command-line arguments
582  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
583  * @param cfg configuration
584  */
585 static void
586 run (void *cls,
587      char *const *args,
588      const char *cfgfile,
589      const struct GNUNET_CONFIGURATION_Handle *c)
590 {
591   GNUNET_HashCode me;
592   char *my_name;
593
594   cfg = c;
595   /* check arguments */
596   if (NULL == nickname)
597     {
598       fprintf (stderr, _("You must specify a nickname\n"));
599       ret = -1;
600       return;
601     }
602   if (NULL == room_name)
603     room_name = GNUNET_strdup ("gnunet");
604   meta = GNUNET_CONTAINER_meta_data_create ();
605   GNUNET_CONTAINER_meta_data_insert (meta,
606                                      "<gnunet>",
607                                      EXTRACTOR_METATYPE_TITLE,
608                                      EXTRACTOR_METAFORMAT_UTF8,
609                                      "text/plain",
610                                      nickname,
611                                      strlen(nickname)+1);
612   room = GNUNET_CHAT_join_room (cfg,
613                                 nickname,
614                                 meta,
615                                 room_name,
616                                 -1,
617                                 &receive_cb, NULL,
618                                 &member_list_cb, NULL,
619                                 &confirmation_cb, NULL, &me);
620   if (NULL == room)
621     {
622       fprintf (stderr, _("Failed to join room `%s'\n"), room_name);
623       GNUNET_free (room_name);
624       GNUNET_free (nickname);
625       GNUNET_CONTAINER_meta_data_destroy (meta);
626       ret = -1;
627       return;
628     }
629   my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
630   fprintf (stdout, _("Joined room `%s' as user `%s'\n"), room_name, my_name);
631   GNUNET_free (my_name);
632   handle_cmd_task =
633     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_UI,
634                                         &handle_command,
635                                         NULL);
636   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
637                                 &do_stop_task,
638                                 NULL);
639 }
640
641
642 /**
643  * The main function to chat via GNUnet.
644  *
645  * @param argc number of arguments from the command line
646  * @param argv command line arguments
647  * @return 0 ok, 1 on error
648  */
649 int
650 main (int argc, char *const *argv)
651 {
652   int flags;
653   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
654     {'n', "nick", "NAME",
655      gettext_noop ("set the nickname to use (required)"),
656      1, &GNUNET_GETOPT_set_string, &nickname},
657     {'r', "room", "NAME",
658      gettext_noop ("set the chat room to join"),
659      1, &GNUNET_GETOPT_set_string, &room_name},
660     GNUNET_GETOPT_OPTION_END
661   };
662
663   flags = fcntl (0, F_GETFL, 0);
664   flags |= O_NONBLOCK;
665   fcntl (0, F_SETFL, flags);
666   return (GNUNET_OK ==
667           GNUNET_PROGRAM_run (argc,
668                               argv,
669                               "gnunet-chat",
670                               gettext_noop ("Join a chat on GNUnet."),
671                               options, &run, NULL)) ? ret : 1;
672 }
673
674 /* end of gnunet-chat.c */