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 =
48 GNUNET_SCHEDULER_NO_TASK;
53 int (*Action) (const char *arguments, const void *xtra);
59 struct UserList *next;
60 struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pkey;
64 static struct UserList *users;
69 struct UserList *next;
79 static int do_help (const char *args, const void *xtra);
83 * Callback used for notification that we have joined the room.
91 fprintf (stdout, _("Joined\n"));
97 * Callback used for notification about incoming messages.
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
110 receive_cb (void *cls,
111 struct GNUNET_CHAT_Room *room,
112 const GNUNET_HashCode * sender,
113 const struct GNUNET_CONTAINER_MetaData *member_info,
115 struct GNUNET_TIME_Absolute timestamp,
116 enum GNUNET_CHAT_MsgOptions options)
123 nick = GNUNET_PSEUDONYM_id_to_name (cfg, sender);
125 nick = GNUNET_strdup (_("anonymous"));
127 switch ((int) options)
129 case GNUNET_CHAT_MSG_OPTION_NONE:
130 case GNUNET_CHAT_MSG_ANONYMOUS:
131 fmt = _("(%s) `%s' said: %s\n");
133 case GNUNET_CHAT_MSG_PRIVATE:
134 fmt = _("(%s) `%s' said to you: %s\n");
136 case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ANONYMOUS:
137 fmt = _("(%s) `%s' said to you: %s\n");
139 case GNUNET_CHAT_MSG_AUTHENTICATED:
140 fmt = _("(%s) `%s' said for sure: %s\n");
142 case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_AUTHENTICATED:
143 fmt = _("(%s) `%s' said to you for sure: %s\n");
145 case GNUNET_CHAT_MSG_ACKNOWLEDGED:
146 fmt = _("(%s) `%s' was confirmed that you received: %s\n");
148 case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED:
149 fmt = _("(%s) `%s' was confirmed that you and only you received: %s\n");
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");
154 case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED:
157 ("(%s) `%s' was confirmed that you and only you received from him or her: %s\n");
159 case GNUNET_CHAT_MSG_OFF_THE_RECORD:
160 fmt = _("(%s) `%s' said off the record: %s\n");
163 fmt = _("(%s) <%s> said using an unknown message type: %s\n");
166 time = GNUNET_STRINGS_absolute_time_to_string (timestamp);
167 fprintf (stdout, fmt, time, nick, message);
175 * Callback used for message delivery confirmations.
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
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)
194 nick = GNUNET_PSEUDONYM_id_to_name (cfg, receiver);
195 fprintf (stdout, _("'%s' acknowledged message #%d\n"), nick, orig_seq_number);
201 * Callback used for notification that another room member has joined or left.
203 * @param cls closure (not used)
204 * @param member_info will be non-null if the member is joining, NULL if he is
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?
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)
218 struct UserList *pos;
219 struct UserList *prev;
221 GNUNET_CRYPTO_hash (member_id,
222 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
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);
228 if (NULL != member_info)
231 pos = GNUNET_malloc (sizeof (struct UserList));
233 pos->pkey = *member_id;
234 pos->ignored = GNUNET_NO;
242 while ((NULL != pos) &&
243 (0 != memcmp (&pos->pkey,
246 GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded))))
260 prev->next = pos->next;
269 do_join (const char *arg, const void *xtra)
275 arg++; /* ignore first hash */
276 GNUNET_CHAT_leave_room (room);
278 GNUNET_free (room_name);
279 room_name = GNUNET_strdup (arg);
280 room = GNUNET_CHAT_join_room (cfg,
287 &member_list_cb, NULL,
288 &confirmation_cb, NULL, &me);
291 fprintf (stdout, _("Could not change username\n"));
292 return GNUNET_SYSERR;
294 my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
295 fprintf (stdout, _("Joining room `%s' as user `%s'...\n"), room_name,
297 GNUNET_free (my_name);
303 do_nick (const char *msg, const void *xtra)
308 GNUNET_CHAT_leave_room (room);
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,
316 EXTRACTOR_METATYPE_TITLE,
317 EXTRACTOR_METAFORMAT_UTF8,
319 nickname, strlen (nickname) + 1);
320 room = GNUNET_CHAT_join_room (cfg,
327 &member_list_cb, NULL,
328 &confirmation_cb, NULL, &me);
331 fprintf (stdout, _("Could not change username\n"));
332 return GNUNET_SYSERR;
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);
342 do_names (const char *msg, const void *xtra)
345 struct UserList *pos;
348 fprintf (stdout, _("Users in room `%s': "), room_name);
352 GNUNET_CRYPTO_hash (&pos->pkey,
353 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
355 name = GNUNET_PSEUDONYM_id_to_name (cfg, &pid);
356 fprintf (stdout, "`%s' ", name);
360 fprintf (stdout, "\n");
366 do_send (const char *msg, const void *xtra)
370 GNUNET_CHAT_send_message (room, msg, GNUNET_CHAT_MSG_OPTION_NONE, NULL, &seq);
376 do_send_pm (const char *msg, const void *xtra)
382 struct UserList *pos;
384 if (NULL == strstr (msg, " "))
386 fprintf (stderr, _("Syntax: /msg USERNAME MESSAGE"));
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))
394 fprintf (stderr, _("Unknown user `%s'\n"), user);
401 GNUNET_CRYPTO_hash (&pos->pkey,
402 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
404 if (0 == memcmp (&pid, &uid, sizeof (GNUNET_HashCode)))
410 fprintf (stderr, _("User `%s' is currently not in the room!\n"), user);
414 GNUNET_CHAT_send_message (room,
415 msg, GNUNET_CHAT_MSG_PRIVATE, &pos->pkey, &seq);
422 do_send_sig (const char *msg, const void *xtra)
426 GNUNET_CHAT_send_message (room,
427 msg, GNUNET_CHAT_MSG_AUTHENTICATED, NULL, &seq);
433 do_send_ack (const char *msg, const void *xtra)
437 GNUNET_CHAT_send_message (room,
438 msg, GNUNET_CHAT_MSG_ACKNOWLEDGED, NULL, &seq);
444 do_send_anonymous (const char *msg, const void *xtra)
448 GNUNET_CHAT_send_message (room, msg, GNUNET_CHAT_MSG_ANONYMOUS, NULL, &seq);
454 do_quit (const char *args, const void *xtra)
456 return GNUNET_SYSERR;
461 do_unknown (const char *msg, const void *xtra)
463 fprintf (stderr, _("Unknown command `%s'\n"), msg);
469 * List of supported IRC commands. The order matters!
471 static struct ChatCommand commands[] = {
474 ("Use `/join #roomname' to join a chat room. Joining a room will cause you"
475 " to leave the current room")},
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,
482 ("Use `/msg nickname message' to send a private message to the specified"
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,
492 ("Use `/ack message' to require signed acknowledgment of the message")},
493 {"/anonymous ", &do_send_anonymous,
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'")},
499 gettext_noop ("Use `/quit' to terminate gnunet-chat")},
501 gettext_noop ("The `/leave' command is an alias for `/quit'")},
502 {"/names", &do_names,
504 ("Use `/names' to list all of the current members in the chat room")},
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},
518 do_help (const char *args, const void *xtra)
523 while ((NULL != args) &&
524 (0 != strlen (args)) && (commands[i].Action != &do_help))
526 if (0 == strncasecmp (&args[1], &commands[i].command[1], strlen (args) - 1))
528 fprintf (stdout, "%s\n", gettext (commands[i].helptext));
534 fprintf (stdout, "Available commands:");
535 while (commands[i].Action != &do_help)
537 fprintf (stdout, " %s", gettext (commands[i].command));
540 fprintf (stdout, "\n");
541 fprintf (stdout, "%s\n", gettext (commands[i].helptext));
547 do_stop_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
549 GNUNET_CHAT_leave_room (room);
550 if (handle_cmd_task != GNUNET_SCHEDULER_NO_TASK)
552 GNUNET_SCHEDULER_cancel (handle_cmd_task);
553 handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
556 GNUNET_CONTAINER_meta_data_destroy (meta);
557 GNUNET_free (room_name);
558 GNUNET_free (nickname);
563 handle_command (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
565 char message[MAX_MESSAGE_LENGTH + 1];
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))
572 if (strlen (message) == 0)
574 if (message[strlen (message) - 1] == '\n')
575 message[strlen (message) - 1] = '\0';
576 if (strlen (message) == 0)
579 while ((NULL != commands[i].command) &&
580 (0 != strncasecmp (commands[i].command,
581 message, strlen (commands[i].command))))
584 commands[i].Action (&message[strlen (commands[i].command)], NULL))
589 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
590 (GNUNET_TIME_UNIT_MILLISECONDS, 100),
591 &handle_command, NULL);
595 handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
596 GNUNET_SCHEDULER_shutdown ();
601 * Main function that will be run by the scheduler.
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
611 const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *c)
617 /* check arguments */
618 if (NULL == nickname)
620 fprintf (stderr, _("You must specify a nickname\n"));
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,
629 EXTRACTOR_METATYPE_TITLE,
630 EXTRACTOR_METAFORMAT_UTF8,
632 nickname, strlen (nickname) + 1);
633 room = GNUNET_CHAT_join_room (cfg,
640 &member_list_cb, NULL,
641 &confirmation_cb, NULL, &me);
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);
651 my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
652 fprintf (stdout, _("Joining room `%s' as user `%s'...\n"), room_name,
654 GNUNET_free (my_name);
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);
664 * The main function to chat via GNUnet.
666 * @param argc number of arguments from the command line
667 * @param argv command line arguments
668 * @return 0 ok, 1 on error
671 main (int argc, char *const *argv)
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
686 flags = fcntl (0, F_GETFL, 0);
688 fcntl (0, F_SETFL, flags);
691 GNUNET_PROGRAM_run (argc,
694 gettext_noop ("Join a chat on GNUnet."),
695 options, &run, NULL)) ? ret : 1;
698 /* end of gnunet-chat.c */