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