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