2 This file is part of GNUnet.
3 (C) 2007, 2008, 2011 Christian Grothoff (and other contributing authors)
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.
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.
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.
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
30 #include "gnunet_getopt_lib.h"
31 #include "gnunet_program_lib.h"
32 #include "gnunet_chat_service.h"
37 static const struct GNUNET_CONFIGURATION_Handle *cfg;
39 static char *nickname;
41 static char *room_name;
43 static struct GNUNET_CONTAINER_MetaData *meta;
45 static struct GNUNET_CHAT_Room *room;
47 static GNUNET_SCHEDULER_TaskIdentifier handle_cmd_task;
49 typedef int (*ActionFunction)(const char *argumetns, const void *xtra);
54 ActionFunction Action;
60 struct UserList *next;
61 struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pkey;
65 static struct UserList *users;
70 struct UserList *next;
81 do_help (const char *args, const void *xtra);
85 * Callback used for notification that we have joined the room.
93 FPRINTF (stdout, "%s", _("Joined\n"));
99 * Callback used for notification about incoming messages.
101 * @param cls closure, NULL
102 * @param room in which room was the message received?
103 * @param sender what is the ID of the sender? (maybe NULL)
104 * @param member_info information about the joining member
105 * @param message the message text
106 * @param timestamp time when the member joined
107 * @param options options for the message
108 * @return GNUNET_OK to accept the message now, GNUNET_NO to
109 * accept (but user is away), GNUNET_SYSERR to signal denied delivery
112 receive_cb (void *cls, struct GNUNET_CHAT_Room *room,
113 const struct GNUNET_HashCode * sender,
114 const struct GNUNET_CONTAINER_MetaData *member_info,
115 const char *message, struct GNUNET_TIME_Absolute timestamp,
116 enum GNUNET_CHAT_MsgOptions options)
118 char *non_unique_nick;
125 nick = GNUNET_strdup (_("anonymous"));
128 if (GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
129 sender, NULL, NULL, &non_unique_nick, &nick_is_a_dup)
130 || (nick_is_a_dup == GNUNET_YES))
132 GNUNET_free (non_unique_nick);
133 non_unique_nick = GNUNET_strdup (_("anonymous"));
135 nick = GNUNET_PSEUDONYM_name_uniquify (cfg, sender, non_unique_nick, NULL);
136 GNUNET_free (non_unique_nick);
140 switch ((int) options)
142 case GNUNET_CHAT_MSG_OPTION_NONE:
143 case GNUNET_CHAT_MSG_ANONYMOUS:
144 fmt = _("(%s) `%s' said: %s\n");
146 case GNUNET_CHAT_MSG_PRIVATE:
147 fmt = _("(%s) `%s' said to you: %s\n");
149 case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ANONYMOUS:
150 fmt = _("(%s) `%s' said to you: %s\n");
152 case GNUNET_CHAT_MSG_AUTHENTICATED:
153 fmt = _("(%s) `%s' said for sure: %s\n");
155 case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_AUTHENTICATED:
156 fmt = _("(%s) `%s' said to you for sure: %s\n");
158 case GNUNET_CHAT_MSG_ACKNOWLEDGED:
159 fmt = _("(%s) `%s' was confirmed that you received: %s\n");
161 case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED:
162 fmt = _("(%s) `%s' was confirmed that you and only you received: %s\n");
164 case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_ACKNOWLEDGED:
165 fmt = _("(%s) `%s' was confirmed that you received from him or her: %s\n");
167 case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED:
170 ("(%s) `%s' was confirmed that you and only you received from him or her: %s\n");
172 case GNUNET_CHAT_MSG_OFF_THE_RECORD:
173 fmt = _("(%s) `%s' said off the record: %s\n");
176 fmt = _("(%s) <%s> said using an unknown message type: %s\n");
179 timestr = GNUNET_STRINGS_absolute_time_to_string (timestamp);
180 FPRINTF (stdout, fmt, timestr, nick, message);
187 * Callback used for message delivery confirmations.
189 * @param cls closure, NULL
190 * @param room in which room was the message received?
191 * @param orig_seq_number sequence number of the original message
192 * @param timestamp when was the message received?
193 * @param receiver who is confirming the receipt?
194 * @return GNUNET_OK to continue, GNUNET_SYSERR to refuse processing further
195 * confirmations from anyone for this message
198 confirmation_cb (void *cls, struct GNUNET_CHAT_Room *room,
199 uint32_t orig_seq_number,
200 struct GNUNET_TIME_Absolute timestamp,
201 const struct GNUNET_HashCode * receiver)
207 if (GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
208 receiver, NULL, NULL, &nick, &nick_is_a_dup)
209 || (nick_is_a_dup == GNUNET_YES))
212 nick = GNUNET_strdup (_("anonymous"));
214 unique_nick = GNUNET_PSEUDONYM_name_uniquify (cfg, receiver, nick, NULL);
216 FPRINTF (stdout, _("'%s' acknowledged message #%d\n"), unique_nick, orig_seq_number);
217 GNUNET_free (unique_nick);
223 * Callback used for notification that another room member has joined or left.
225 * @param cls closure (not used)
226 * @param member_info will be non-null if the member is joining, NULL if he is
228 * @param member_id hash of public key of the user (for unique identification)
229 * @param options what types of messages is this member willing to receive?
233 member_list_cb (void *cls, const struct GNUNET_CONTAINER_MetaData *member_info,
234 const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *member_id,
235 enum GNUNET_CHAT_MsgOptions options)
238 char *non_unique_nick;
240 struct GNUNET_HashCode id;
241 struct UserList *pos;
242 struct UserList *prev;
244 GNUNET_CRYPTO_hash (member_id,
245 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
247 if (GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
248 &id, NULL, NULL, &non_unique_nick, &nick_is_a_dup)
249 || (nick_is_a_dup == GNUNET_YES))
251 GNUNET_free (non_unique_nick);
252 non_unique_nick = GNUNET_strdup (_("anonymous"));
254 nick = GNUNET_PSEUDONYM_name_uniquify (cfg, &id, non_unique_nick, NULL);
255 GNUNET_free (non_unique_nick);
259 NULL ? _("`%s' entered the room\n") : _("`%s' left the room\n"),
262 if (NULL != member_info)
265 pos = GNUNET_malloc (sizeof (struct UserList));
267 pos->pkey = *member_id;
268 pos->ignored = GNUNET_NO;
276 while ((NULL != pos) &&
278 memcmp (&pos->pkey, member_id,
279 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded))))
293 prev->next = pos->next;
302 do_join (const char *arg, const void *xtra)
305 int my_name_is_a_dup;
306 struct GNUNET_HashCode me;
309 arg++; /* ignore first hash */
310 GNUNET_CHAT_leave_room (room);
312 GNUNET_free (room_name);
313 room_name = GNUNET_strdup (arg);
315 GNUNET_CHAT_join_room (cfg, nickname, meta, room_name, -1, &join_cb, NULL,
316 &receive_cb, NULL, &member_list_cb, NULL,
317 &confirmation_cb, NULL, &me);
320 FPRINTF (stdout, "%s", _("Could not change username\n"));
321 return GNUNET_SYSERR;
323 if ((GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
324 &me, NULL, NULL, &my_name, &my_name_is_a_dup)) ||
325 (my_name_is_a_dup == GNUNET_YES))
327 GNUNET_free (my_name);
328 my_name = GNUNET_strdup (_("anonymous"));
330 /* Don't uniquify our own name - other people will have a different
331 * suffix for our own name anyway.
333 FPRINTF (stdout, _("Joining room `%s' as user `%s'...\n"), room_name,
335 GNUNET_free (my_name);
341 do_nick (const char *msg, const void *xtra)
344 int my_name_is_a_dup;
345 struct GNUNET_HashCode me;
347 GNUNET_CHAT_leave_room (room);
349 GNUNET_free (nickname);
350 GNUNET_CONTAINER_meta_data_destroy (meta);
351 nickname = GNUNET_strdup (msg);
352 meta = GNUNET_CONTAINER_meta_data_create ();
353 GNUNET_CONTAINER_meta_data_insert (meta, "<gnunet>", EXTRACTOR_METATYPE_TITLE,
354 EXTRACTOR_METAFORMAT_UTF8, "text/plain",
355 nickname, strlen (nickname) + 1);
357 GNUNET_CHAT_join_room (cfg, nickname, meta, room_name, -1, &join_cb, NULL,
358 &receive_cb, NULL, &member_list_cb, NULL,
359 &confirmation_cb, NULL, &me);
362 FPRINTF (stdout, "%s", _("Could not change username\n"));
363 return GNUNET_SYSERR;
365 if ((GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
366 &me, NULL, NULL, &my_name, &my_name_is_a_dup)) ||
367 (my_name_is_a_dup == GNUNET_YES))
369 GNUNET_free (my_name);
370 my_name = GNUNET_strdup (_("anonymous"));
372 FPRINTF (stdout, _("Changed username to `%s'\n"), my_name);
373 GNUNET_free (my_name);
379 do_names (const char *msg, const void *xtra)
384 struct UserList *pos;
385 struct GNUNET_HashCode pid;
387 FPRINTF (stdout, _("Users in room `%s': "), room_name);
391 GNUNET_CRYPTO_hash (&pos->pkey,
392 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
394 if (GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
395 &pid, NULL, NULL, &name, &name_is_a_dup)
396 || (name_is_a_dup == GNUNET_YES))
399 name = GNUNET_strdup (_("anonymous"));
401 unique_name = GNUNET_PSEUDONYM_name_uniquify (cfg, &pid, name, NULL);
403 FPRINTF (stdout, "`%s' ", unique_name);
404 GNUNET_free (unique_name);
407 FPRINTF (stdout, "%s", "\n");
413 do_send (const char *msg, const void *xtra)
417 GNUNET_CHAT_send_message (room, msg, GNUNET_CHAT_MSG_OPTION_NONE, NULL, &seq);
423 do_send_pm (const char *msg, const void *xtra)
426 struct GNUNET_HashCode uid;
427 struct GNUNET_HashCode pid;
429 struct UserList *pos;
431 if (NULL == strstr (msg, " "))
433 FPRINTF (stderr, "%s", _("Syntax: /msg USERNAME MESSAGE"));
436 user = GNUNET_strdup (msg);
437 strstr (user, " ")[0] = '\0';
438 msg += strlen (user) + 1;
439 if (GNUNET_OK != GNUNET_PSEUDONYM_name_to_id (cfg, user, &uid))
442 _("Unknown user `%s'. Make sure you specify its numeric suffix, if any.\n"),
450 GNUNET_CRYPTO_hash (&pos->pkey,
451 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
453 if (0 == memcmp (&pid, &uid, sizeof (struct GNUNET_HashCode)))
459 FPRINTF (stderr, _("User `%s' is currently not in the room!\n"), user);
463 GNUNET_CHAT_send_message (room, msg, GNUNET_CHAT_MSG_PRIVATE, &pos->pkey,
471 do_send_sig (const char *msg, const void *xtra)
475 GNUNET_CHAT_send_message (room, msg, GNUNET_CHAT_MSG_AUTHENTICATED, NULL,
482 do_send_ack (const char *msg, const void *xtra)
486 GNUNET_CHAT_send_message (room, msg, GNUNET_CHAT_MSG_ACKNOWLEDGED, NULL,
493 do_send_anonymous (const char *msg, const void *xtra)
497 GNUNET_CHAT_send_message (room, msg, GNUNET_CHAT_MSG_ANONYMOUS, NULL, &seq);
503 do_quit (const char *args, const void *xtra)
505 return GNUNET_SYSERR;
510 do_unknown (const char *msg, const void *xtra)
512 FPRINTF (stderr, _("Unknown command `%s'\n"), msg);
518 * List of supported IRC commands. The order matters!
520 static struct ChatCommand commands[] = {
523 ("Use `/join #roomname' to join a chat room. Joining a room will cause you"
524 " to leave the current room")},
527 ("Use `/nick nickname' to change your nickname. This will cause you to"
528 " leave the current room and immediately rejoin it with the new name.")},
529 {"/msg ", &do_send_pm,
531 ("Use `/msg nickname message' to send a private message to the specified"
533 {"/notice ", &do_send_pm,
534 gettext_noop ("The `/notice' command is an alias for `/msg'")},
535 {"/query ", &do_send_pm,
536 gettext_noop ("The `/query' command is an alias for `/msg'")},
537 {"/sig ", &do_send_sig,
538 gettext_noop ("Use `/sig message' to send a signed public message")},
539 {"/ack ", &do_send_ack,
541 ("Use `/ack message' to require signed acknowledgment of the message")},
542 {"/anonymous ", &do_send_anonymous,
544 ("Use `/anonymous message' to send a public anonymous message")},
545 {"/anon ", &do_send_anonymous,
546 gettext_noop ("The `/anon' command is an alias for `/anonymous'")},
548 gettext_noop ("Use `/quit' to terminate gnunet-chat")},
550 gettext_noop ("The `/leave' command is an alias for `/quit'")},
551 {"/names", &do_names,
553 ("Use `/names' to list all of the current members in the chat room")},
555 gettext_noop ("Use `/help command' to get help for a specific command")},
556 /* Add standard commands:
557 * /whois (print metadata),
558 * /ignore (set flag, check on receive!) */
559 /* the following three commands must be last! */
560 {"/", &do_unknown, NULL},
561 {"", &do_send, NULL},
567 do_help (const char *args, const void *xtra)
572 while ((NULL != args) && (0 != strlen (args)) &&
573 (commands[i].Action != &do_help))
575 if (0 == strncasecmp (&args[1], &commands[i].command[1], strlen (args) - 1))
577 FPRINTF (stdout, "%s\n", gettext (commands[i].helptext));
583 FPRINTF (stdout, "%s", "Available commands:");
584 while (commands[i].Action != &do_help)
586 FPRINTF (stdout, " %s", gettext (commands[i].command));
589 FPRINTF (stdout, "%s", "\n");
590 FPRINTF (stdout, "%s\n", gettext (commands[i].helptext));
596 do_stop_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
598 GNUNET_CHAT_leave_room (room);
599 if (handle_cmd_task != GNUNET_SCHEDULER_NO_TASK)
601 GNUNET_SCHEDULER_cancel (handle_cmd_task);
602 handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
605 GNUNET_CONTAINER_meta_data_destroy (meta);
606 GNUNET_free (room_name);
607 GNUNET_free (nickname);
612 handle_command (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
614 char message[MAX_MESSAGE_LENGTH + 1];
617 /* read message from command line and handle it */
618 memset (message, 0, MAX_MESSAGE_LENGTH + 1);
619 if (NULL == fgets (message, MAX_MESSAGE_LENGTH, stdin))
621 if (strlen (message) == 0)
623 if (message[strlen (message) - 1] == '\n')
624 message[strlen (message) - 1] = '\0';
625 if (strlen (message) == 0)
628 while ((NULL != commands[i].command) &&
630 strncasecmp (commands[i].command, message,
631 strlen (commands[i].command))))
634 commands[i].Action (&message[strlen (commands[i].command)], NULL))
639 GNUNET_SCHEDULER_add_delayed_with_priority (GNUNET_TIME_relative_multiply
640 (GNUNET_TIME_UNIT_MILLISECONDS, 100),
641 GNUNET_SCHEDULER_PRIORITY_UI,
642 &handle_command, NULL);
646 handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
647 GNUNET_SCHEDULER_shutdown ();
652 * Main function that will be run by the scheduler.
654 * @param cls closure, NULL
655 * @param args remaining command-line arguments
656 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
657 * @param c configuration
660 run (void *cls, char *const *args, const char *cfgfile,
661 const struct GNUNET_CONFIGURATION_Handle *c)
663 struct GNUNET_HashCode me;
665 int my_name_is_a_dup;
668 /* check arguments */
669 if (NULL == nickname)
671 FPRINTF (stderr, "%s", _("You must specify a nickname\n"));
675 if (NULL == room_name)
676 room_name = GNUNET_strdup ("gnunet");
677 meta = GNUNET_CONTAINER_meta_data_create ();
678 GNUNET_CONTAINER_meta_data_insert (meta, "<gnunet>", EXTRACTOR_METATYPE_TITLE,
679 EXTRACTOR_METAFORMAT_UTF8, "text/plain",
680 nickname, strlen (nickname) + 1);
682 GNUNET_CHAT_join_room (cfg, nickname, meta, room_name, -1, &join_cb, NULL,
683 &receive_cb, NULL, &member_list_cb, NULL,
684 &confirmation_cb, NULL, &me);
687 FPRINTF (stderr, _("Failed to join room `%s'\n"), room_name);
688 GNUNET_free (room_name);
689 GNUNET_free (nickname);
690 GNUNET_CONTAINER_meta_data_destroy (meta);
694 if ((GNUNET_OK != GNUNET_PSEUDONYM_get_info (cfg,
695 &me, NULL, NULL, &my_name, &my_name_is_a_dup)) ||
696 (my_name_is_a_dup == GNUNET_YES))
698 GNUNET_free (my_name);
699 my_name = GNUNET_strdup (_("anonymous"));
701 FPRINTF (stdout, _("Joining room `%s' as user `%s'...\n"), room_name,
703 GNUNET_free (my_name);
705 GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_UI,
706 &handle_command, NULL);
707 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &do_stop_task,
713 * The main function to chat via GNUnet.
715 * @param argc number of arguments from the command line
716 * @param argv command line arguments
717 * @return 0 ok, 1 on error
720 main (int argc, char *const *argv)
724 static const struct GNUNET_GETOPT_CommandLineOption options[] = {
725 {'n', "nick", "NAME",
726 gettext_noop ("set the nickname to use (required)"),
727 1, &GNUNET_GETOPT_set_string, &nickname},
728 {'r', "room", "NAME",
729 gettext_noop ("set the chat room to join"),
730 1, &GNUNET_GETOPT_set_string, &room_name},
731 GNUNET_GETOPT_OPTION_END
735 flags = fcntl (0, F_GETFL, 0);
737 fcntl (0, F_SETFL, flags);
740 if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
744 GNUNET_PROGRAM_run (argc, argv, "gnunet-chat",
745 gettext_noop ("Join a chat on GNUnet."), options,
746 &run, NULL)) ? ret : 1;
749 /* end of gnunet-chat.c */