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