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