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