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 = GNUNET_SCHEDULER_NO_TASK;
52 int (*Action) (const char *arguments, const void *xtra);
58 struct UserList *next;
59 struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pkey;
63 static struct UserList *users;
68 struct UserList *next;
77 static int do_help (const char *args, const void *xtra);
81 * Callback used for notification about incoming messages.
83 * @param cls closure, NULL
84 * @param room in which room was the message received?
85 * @param sender what is the ID of the sender? (maybe NULL)
86 * @param meta information about the joining member
87 * @param message the message text
88 * @param options options for the message
89 * @return GNUNET_OK to accept the message now, GNUNET_NO to
90 * accept (but user is away), GNUNET_SYSERR to signal denied delivery
93 receive_cb (void *cls,
94 struct GNUNET_CHAT_Room *room,
95 const GNUNET_HashCode *sender,
96 const struct GNUNET_CONTAINER_MetaData *meta,
98 enum GNUNET_CHAT_MsgOptions options)
104 nick = GNUNET_PSEUDONYM_id_to_name (cfg, sender);
106 nick = GNUNET_strdup (_("anonymous"));
110 case GNUNET_CHAT_MSG_OPTION_NONE:
111 case GNUNET_CHAT_MSG_ANONYMOUS:
112 fmt = _("`%s' said: %s\n");
114 case GNUNET_CHAT_MSG_PRIVATE:
115 fmt = _("`%s' said to you: %s\n");
117 case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ANONYMOUS:
118 fmt = _("`%s' said to you: %s\n");
120 case GNUNET_CHAT_MSG_AUTHENTICATED:
121 fmt = _("`%s' said for sure: %s\n");
123 case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_AUTHENTICATED:
124 fmt = _("`%s' said to you for sure: %s\n");
126 case GNUNET_CHAT_MSG_ACKNOWLEDGED:
127 fmt = _("`%s' was confirmed that you received: %s\n");
129 case GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED:
130 fmt = _("`%s' was confirmed that you and only you received: %s\n");
132 case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_ACKNOWLEDGED:
133 fmt = _("`%s' was confirmed that you received from him or her: %s\n");
135 case GNUNET_CHAT_MSG_AUTHENTICATED | GNUNET_CHAT_MSG_PRIVATE | GNUNET_CHAT_MSG_ACKNOWLEDGED:
138 ("`%s' was confirmed that you and only you received from him or her: %s\n");
140 case GNUNET_CHAT_MSG_OFF_THE_RECORD:
141 fmt = _("`%s' said off the record: %s\n");
144 fmt = _("<%s> said using an unknown message type: %s\n");
147 fprintf (stdout, fmt, nick, message);
154 * Callback used for message delivery confirmations.
156 * @param cls closure, NULL
157 * @param room in which room was the message received?
158 * @param orig_seq_number sequence number of the original message
159 * @param timestamp when was the message received?
160 * @param receiver who is confirming the receipt?
161 * @param msg_hash hash of the original message
162 * @param receipt signature confirming delivery
163 * @return GNUNET_OK to continue, GNUNET_SYSERR to refuse processing further
164 * confirmations from anyone for this message
167 confirmation_cb (void *cls,
168 struct GNUNET_CHAT_Room *room,
169 uint32_t orig_seq_number,
170 struct GNUNET_TIME_Absolute timestamp,
171 const GNUNET_HashCode *receiver,
172 const GNUNET_HashCode *msg_hash,
173 const struct GNUNET_CRYPTO_RsaSignature *receipt)
177 nick = GNUNET_PSEUDONYM_id_to_name (cfg, receiver);
178 fprintf (stdout, _("'%s' acknowledged message #%d\n"), nick, orig_seq_number);
184 * Callback used for notification that another room member has joined or left.
186 * @param cls closure (not used)
187 * @param member_info will be non-null if the member is joining, NULL if he is
189 * @param member_id hash of public key of the user (for unique identification)
190 * @param options what types of messages is this member willing to receive?
194 member_list_cb (void *cls,
195 const struct GNUNET_CONTAINER_MetaData *member_info,
196 const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *member_id,
197 enum GNUNET_CHAT_MsgOptions options)
201 struct UserList *pos;
202 struct UserList *prev;
204 GNUNET_CRYPTO_hash (member_id,
205 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
207 nick = GNUNET_PSEUDONYM_id_to_name (cfg, &id);
208 fprintf (stdout, member_info != NULL
209 ? _("`%s' entered the room\n") : _("`%s' left the room\n"), nick);
211 if (NULL != member_info)
214 pos = GNUNET_malloc (sizeof (struct UserList));
216 pos->pkey = *member_id;
217 pos->ignored = GNUNET_NO;
225 while ((NULL != pos) &&
226 (0 != memcmp (&pos->pkey,
228 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded))))
242 prev->next = pos->next;
251 do_transmit (const char *msg, const void *xtra)
254 GNUNET_CHAT_send_message (room,
256 GNUNET_CHAT_MSG_OPTION_NONE,
263 do_join (const char *arg, const void *xtra)
269 arg++; /* ignore first hash */
270 GNUNET_CHAT_leave_room (room);
272 GNUNET_free (room_name);
273 room_name = GNUNET_strdup (arg);
274 room = GNUNET_CHAT_join_room (cfg,
280 &member_list_cb, NULL,
281 &confirmation_cb, NULL, &me);
284 fprintf (stdout, _("Could not change username\n"));
285 return GNUNET_SYSERR;
287 my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
288 fprintf (stdout, _("Joined room `%s' as user `%s'\n"), room_name, my_name);
289 GNUNET_free (my_name);
295 do_nick (const char *msg, const void *xtra)
300 GNUNET_CHAT_leave_room (room);
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,
308 EXTRACTOR_METATYPE_TITLE,
309 EXTRACTOR_METAFORMAT_UTF8,
313 room = GNUNET_CHAT_join_room (cfg,
319 &member_list_cb, NULL,
320 &confirmation_cb, NULL, &me);
323 fprintf (stdout, _("Could not change username\n"));
324 return GNUNET_SYSERR;
326 my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
327 fprintf (stdout, _("Changed username to `%s'\n"), my_name);
328 GNUNET_free (my_name);
334 do_names (const char *msg, const void *xtra)
337 struct UserList *pos;
340 fprintf (stdout, _("Users in room `%s': "), room_name);
344 GNUNET_CRYPTO_hash (&pos->pkey,
345 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
347 name = GNUNET_PSEUDONYM_id_to_name (cfg, &pid);
348 fprintf (stdout, "`%s' ", name);
352 fprintf (stdout, "\n");
358 do_pm (const char *msg, const void *xtra)
364 struct UserList *pos;
366 if (NULL == strstr (msg, " "))
368 fprintf (stderr, _("Syntax: /msg USERNAME MESSAGE"));
371 user = GNUNET_strdup (msg);
372 strstr (user, " ")[0] = '\0';
373 msg += strlen (user) + 1;
374 if (GNUNET_OK != GNUNET_PSEUDONYM_name_to_id (cfg, user, &uid))
376 fprintf (stderr, _("Unknown user `%s'\n"), user);
383 GNUNET_CRYPTO_hash (&pos->pkey,
384 sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
386 if (0 == memcmp (&pid, &uid, sizeof (GNUNET_HashCode)))
392 fprintf (stderr, _("User `%s' is currently not in the room!\n"), user);
396 GNUNET_CHAT_send_message (room,
398 GNUNET_CHAT_MSG_PRIVATE,
407 do_transmit_sig (const char *msg, const void *xtra)
410 GNUNET_CHAT_send_message (room,
412 GNUNET_CHAT_MSG_AUTHENTICATED,
419 do_transmit_ack (const char *msg, const void *xtra)
422 GNUNET_CHAT_send_message (room,
424 GNUNET_CHAT_MSG_ACKNOWLEDGED,
431 do_quit (const char *args, const void *xtra)
433 return GNUNET_SYSERR;
438 do_unknown (const char *msg, const void *xtra)
440 fprintf (stderr, _("Unknown command `%s'\n"), msg);
446 * List of supported IRC commands. The order matters!
448 static struct ChatCommand commands[] = {
451 ("Use `/join #roomname' to join a chat room. Joining a room will cause you"
452 " to leave the current room")},
455 ("Use `/nick nickname' to change your nickname. This will cause you to"
456 " leave the current room and immediately rejoin it with the new name.")},
459 ("Use `/msg nickname message' to send a private message to the specified"
462 gettext_noop ("The `/notice' command is an alias for `/msg'")},
464 gettext_noop ("The `/query' command is an alias for `/msg'")},
465 {"/sig ", &do_transmit_sig,
466 gettext_noop ("Use `/sig message' to send a signed public message")},
467 {"/ack ", &do_transmit_ack,
469 ("Use `/ack message' to require signed acknowledgment of the message")},
471 gettext_noop ("Use `/quit' to terminate gnunet-chat")},
473 gettext_noop ("The `/leave' command is an alias for `/quit'")},
474 {"/names", &do_names,
476 ("Use `/names' to list all of the current members in the chat room")},
478 gettext_noop ("Use `/help command' to get help for a specific command")},
479 /* Add standard commands:
480 /whois (print metadata),
481 /ignore (set flag, check on receive!) */
482 /* Add special commands (currently supported):
486 /* the following three commands must be last! */
487 {"/", &do_unknown, NULL},
488 {"", &do_transmit, NULL},
494 do_help (const char *args, const void *xtra)
498 while ((NULL != args) &&
499 (0 != strlen (args)) && (commands[i].Action != &do_help))
502 strncasecmp (&args[1], &commands[i].command[1], strlen (args) - 1))
504 fprintf (stdout, "%s\n", gettext (commands[i].helptext));
510 fprintf (stdout, "Available commands:");
511 while (commands[i].Action != &do_help)
513 fprintf (stdout, " %s", gettext (commands[i].command));
516 fprintf (stdout, "\n");
517 fprintf (stdout, "%s\n", gettext (commands[i].helptext));
523 do_stop_task (void *cls,
524 const struct GNUNET_SCHEDULER_TaskContext *tc)
526 GNUNET_CHAT_leave_room (room);
527 if (handle_cmd_task != GNUNET_SCHEDULER_NO_TASK)
529 GNUNET_SCHEDULER_cancel (handle_cmd_task);
530 handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
533 GNUNET_CONTAINER_meta_data_destroy (meta);
534 GNUNET_free (room_name);
535 GNUNET_free (nickname);
540 handle_command (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
542 char message[MAX_MESSAGE_LENGTH + 1];
545 /* read message from command line and handle it */
546 memset (message, 0, MAX_MESSAGE_LENGTH + 1);
547 if (NULL == fgets (message, MAX_MESSAGE_LENGTH, stdin))
549 if (strlen (message) == 0)
551 if (message[strlen (message) - 1] == '\n')
552 message[strlen (message) - 1] = '\0';
553 if (strlen (message) == 0)
556 while ((NULL != commands[i].command) &&
557 (0 != strncasecmp (commands[i].command,
558 message, strlen (commands[i].command))))
561 commands[i].Action (&message[strlen (commands[i].command)], NULL))
566 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
573 handle_cmd_task = GNUNET_SCHEDULER_NO_TASK;
574 GNUNET_SCHEDULER_shutdown ();
579 * Main function that will be run by the scheduler.
581 * @param cls closure, NULL
582 * @param args remaining command-line arguments
583 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
584 * @param c configuration
590 const struct GNUNET_CONFIGURATION_Handle *c)
596 /* check arguments */
597 if (NULL == nickname)
599 fprintf (stderr, _("You must specify a nickname\n"));
603 if (NULL == room_name)
604 room_name = GNUNET_strdup ("gnunet");
605 meta = GNUNET_CONTAINER_meta_data_create ();
606 GNUNET_CONTAINER_meta_data_insert (meta,
608 EXTRACTOR_METATYPE_TITLE,
609 EXTRACTOR_METAFORMAT_UTF8,
613 room = GNUNET_CHAT_join_room (cfg,
619 &member_list_cb, NULL,
620 &confirmation_cb, NULL, &me);
623 fprintf (stderr, _("Failed to join room `%s'\n"), room_name);
624 GNUNET_free (room_name);
625 GNUNET_free (nickname);
626 GNUNET_CONTAINER_meta_data_destroy (meta);
630 my_name = GNUNET_PSEUDONYM_id_to_name (cfg, &me);
631 fprintf (stdout, _("Joined room `%s' as user `%s'\n"), room_name, my_name);
632 GNUNET_free (my_name);
634 GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_UI,
637 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
644 * The main function to chat via GNUnet.
646 * @param argc number of arguments from the command line
647 * @param argv command line arguments
648 * @return 0 ok, 1 on error
651 main (int argc, char *const *argv)
654 static const struct GNUNET_GETOPT_CommandLineOption options[] = {
655 {'n', "nick", "NAME",
656 gettext_noop ("set the nickname to use (required)"),
657 1, &GNUNET_GETOPT_set_string, &nickname},
658 {'r', "room", "NAME",
659 gettext_noop ("set the chat room to join"),
660 1, &GNUNET_GETOPT_set_string, &room_name},
661 GNUNET_GETOPT_OPTION_END
665 flags = fcntl (0, F_GETFL, 0);
667 fcntl (0, F_SETFL, flags);
670 GNUNET_PROGRAM_run (argc,
673 gettext_noop ("Join a chat on GNUnet."),
674 options, &run, NULL)) ? ret : 1;
677 /* end of gnunet-chat.c */